Groups

Push groups — subscribe subscriber ids and/or device keys to a keyed group from any framework, read the members, and send to the whole group with one call. Every app also has three always-on groups (Lapsed, Active, New), filled automatically from app opens.

A group is an app-scoped, keyed audience — weekly-news, bakery-specials, volunteers — that any client can subscribe to. Subscribe once, then send to the group by its key.

The three always-on groups

Every app always has three groups. There is nothing to create and nothing to turn on:

KeyWho is in itRule
lapsedNo app open in the last 30 dayslapsed_30d
activeOpened the app in the last 7 daysactive_7d
newFirst app open in the last 7 daysnew_7d

Membership is computed live, by subscriber id, from app opens: a device re-registers on every launch, so its subscriber counts as active the moment the app opens. What the counts report is what a send reaches — every member holds at least one live token.

Always-on groups are read-only: they cannot be subscribed to, edited or deleted, and their keys are reserved. Send to them — "audience": { "type": "group", "key": "lapsed" } — or schedule against them like any other group.

The groups you create yourself are manual groups — the rest of this page is about them.

A manual group's members are one of two kinds:

  • A subscriber id — an identity. It reaches every device registered under that subscriberId, now and in the future (new phones and reinstalls included).
  • A device key — one registered deviceId, for anonymous audiences that have no subscriber identity (devices registered without a subscriberId, such as browsers).

Subscribing is idempotent: subscribing an existing member is a no-op (joined: false), so a client can subscribe on every launch.

Not the classic topic groups:

Universal groups are separate from the classic Expo path's topic groups: a different audience and send pipeline, and they do not interact.

All endpoints use your app id + app token — in the body for the POSTs, in the URL for the GETs.

Subscribe

POST https://app.nativenotify.com/api/universal/groups/subscribe
{
  "appId": 123,
  "appToken": "yourAppToken",
  "key": "weekly-news",
  "name": "Weekly News",
  "subscriberId": "user_8241"
}
FieldRequiredNotes
appId, appTokenyesYour app's pair.
keyyes1–64 characters: lowercase letters, digits, dashes and underscores, starting with a letter or digit. Aliases: groupKey, group.
subscriberIdone memberOne subscriber id (max 256 chars). Aliases: subscriber_id, subId, sub_id.
deviceIdone memberOne registered device key (max 200 chars). Alias: device_id.
subscribersone memberBulk subscriber ids, up to 500 per call. Aliases: subscriberIds, subscriber_ids.
devicesone memberBulk device keys, up to 500 per call. Aliases: deviceIds, device_ids.
name, descriptionnoUsed only when this call creates the group (max 120 / 500 chars). The name defaults to the key.
createnoDefault true: an unknown key creates the group. Pass false to answer 404 group_not_found instead.

Send at least one member; the four member fields can be mixed in one call. More than 500 ids of one kind is a 400 too_many_members — the batch is rejected, never truncated.

{
  "ok": true,
  "group": { "id": 17, "key": "weekly-news", "name": "Weekly News", "created": true },
  "results": { "subscribers": [{ "subscriberId": "user_8241", "joined": true }], "devices": [] },
  "counts": { "subscribers": 1, "devices": 0, "resolvableDevices": 2, "prunableDevices": 0 }
}

Unsubscribe

POST https://app.nativenotify.com/api/universal/groups/unsubscribe

Same body as subscribe (key plus members). Answers 200 with removed per member (false when the member was not in the group) and the new counts; an unknown key is 404 group_not_found.

Read groups and members

GET https://app.nativenotify.com/api/universal/groups/:appId/:appToken
GET https://app.nativenotify.com/api/universal/groups/:appId/:appToken/:key
GET https://app.nativenotify.com/api/universal/groups/:appId/:appToken/:key/members?take=100&skip=0

The list returns every group on the app, oldest first, each with id, key, name, description, createdBy, createdAt, updatedAt and counts. One group returns a single group by key (or by its numeric id when no group has that key), or 404 group_not_found. Members returns one page of membership rows — { memberId, subscriberId, deviceId, source, joinedAt, resolvedDevices } — with total, take and skip. take defaults to 100 and must be an integer 1..200 (400 invalid_take), and total is the full membership count, so page with skip until you have read every row. For an always-on group the page is its computed members: read-only, source: "auto", memberId is null, and rows are ordered by subscriber id.

CountMeaning
subscribers / devicesMembership rows of each kind.
resolvableDevicesDistinct registered devices with at least one live token that the memberships reach right now (across all environments — a send reaches only the ones in its own environment).
prunableDevicesDevice memberships whose device is not registered. Never removed automatically; the dashboard's Prune action removes exactly those rows. Subscriber memberships are never prunable.

For an always-on group, subscribers and resolvableDevices are its computed member count — it stores no membership rows, and devices is always 0.

Send to a group

A group is one audience of the ordinary send:

{
  "appId": 123,
  "appToken": "yourAppToken",
  "title": "This week's newsletter",
  "message": "New recipes from the bakery and the library's reading list.",
  "audience": { "type": "group", "key": "weekly-news" }
}

At send time the memberships resolve to live tokens in the send's environment (production when omitted). A device reached by both a subscriber membership and a device membership gets the push once. An unknown or empty group reports devices: 0, so check it.

Sending to a group is a Pro plan feature: on Premium the send answers 403 plan_upgrade_required and nothing is sent. Subscribing, unsubscribing and reading groups work on every plan.

Errors

StatusCodeMeaning
400invalid_body, missing_field, invalid_fieldBad body, no key, a bad appId, or a name / description that is too long.
400invalid_group_keyThe key does not match the rules above.
400invalid_subscriber_id, invalid_device_idAn empty or too-long member id, or a member list that is not an array.
400no_members, too_many_membersNo member at all, or more than 500 of one kind.
400auto_groupA subscribe/unsubscribe named one of the always-on groups (lapsed, active, new) — they fill themselves.
400reserved_group_keyA group key reserved for the always-on groups was used to create a manual group.
400invalid_take, invalid_skipBad member-page parameters.
404app_not_found, group_not_foundThe app is not one this caller can access, or the key does not exist.
409key_reserved, system_groupThe dashboard-facing codes: a create named a reserved key, or an edit/delete targeted an always-on group.
502subscribe_failed, unsubscribe_failedThe change could not be saved — retry.

The dashboard's Groups tab lists every group — the three always-on ones first, with live counts — and can create, edit, and prune manual groups; the MCP server exposes the same surface as tools (list_universal_groups, subscribe_to_universal_group, send_notification_to_universal_group, …).