Subscribers API
The Subscribers API is how you manage the devices that receive your pushes. Most of these routes are called by the SDK on your behalf — the ones you'll touch from your own backend are identify, tag, and untag.
From a Node backend, use the Server SDK (
@reachbell/node) instead of calling these endpoints directly — it wraps them with typed helpers and error handling. The raw REST below is for other languages and for endpoints the SDK doesn't wrap yet.
All routes require authentication. Use x-api-key for server-to-server calls, or a JWT plus x-org-id for dashboard reads. See Authentication for the details.
Register a subscriber
POST /subscribers
The SDK calls this for you when a visitor accepts the opt-in prompt. You rarely call this directly. If you do, the body must include the push subscription endpoint, the encryption keys, and the project ID.
curl -X POST https://api.reachbell.com/subscribers \
-H "x-api-key: rb_live_yourkey" \
-H "Content-Type: application/json" \
-d '{
"projectId": "proj_01HX2...",
"endpoint": "https://fcm.googleapis.com/fcm/send/...",
"p256dh": "BNc...",
"auth": "tZk...",
"userAgent": "Mozilla/5.0 ..."
}'Success:
{
"id": "sub_01HX3...",
"token": "tok_01HX3...",
"projectId": "proj_01HX2...",
"status": "active"
}A duplicate registration of the same endpoint returns 409 Conflict with the existing subscriber.
Mobile (FCM / APNs) registration
For mobile apps, use a native mobile SDK — iOS, Android, Flutter, and React Native all acquire the device's push token and register it for you (the same register() / identify() / tag() surface as the browser SDK). Reach for the raw POST /subscribers call below only as a fallback — for a platform no SDK covers yet, or when you already hold the device token server-side. Send the device token instead of a web-push subscription.
curl -X POST https://api.reachbell.com/subscribers \
-H "x-api-key: rb_live_yourkey" \
-H "Content-Type: application/json" \
-d '{
"token": "<device-token>",
"tokenType": "fcm",
"platform": "android",
"externalId": "user_42"
}'token— the device registration token (FCM) or device token (APNs). Required.tokenType—fcmfor Android,apnsfor iOS. Required to route the send to the right provider.platform—androidorios.externalId— optional. Your internal user ID, so you can target by user instead of the raw token.email,phone, andtagsmay also be set on the same call.
The project is resolved from your x-api-key, so you don't pass projectId in the body. As with web push, re-posting the same device token on the same project updates the existing subscriber rather than creating a duplicate.
Identify a subscriber
POST /subscribers/identify
Link a ReachBell subscriber to your own internal user ID. After identifying, you can target by externalId instead of having to track the subscriber token yourself. This is the server-side counterpart of the browser SDK's ReachBell.identify() — x-api-key gated, so a hostile page can't bind arbitrary subscribers to arbitrary user IDs.
Body fields:
token— the subscriber's push token (thetokenreturned fromPOST /subscribers). Required.externalId— your internal user ID. Required.
curl -X POST https://api.reachbell.com/subscribers/identify \
-H "x-api-key: rb_live_yourkey" \
-H "Content-Type: application/json" \
-d '{
"token": "tok_01HX3...",
"externalId": "user_42"
}'Success — updated is true when the call changed the stored externalId, false when it already matched (the call is idempotent):
{
"success": true,
"updated": true
}When no subscriber on the project matches that token, the response is 200 OK with a reason rather than an error:
{
"success": false,
"updated": false,
"reason": "subscriber_not_found"
}Other reason: token_and_externalId_required when either field is missing.
Identify is idempotent: re-identifying with the same
externalIdis a no-op; re-identifying with a different one rebinds the subscription to the new user (typical for shared devices and account switches). The(projectId, token)pair must already exist —identifynever creates a subscriber, so call it only afterPOST /subscribers.
Call
identifyas soon as the user signs in on your site. Without anexternalIdyou can only address the subscriber by their opaque token, and you lose the link if they switch devices.
Tag a subscriber
POST /subscribers/tag
Tags are free-form labels you attach to a subscriber. Segments use them as filters. Common patterns: lifecycle stage (trial, paid), product interest (shoes, bags), or behavior (abandoned-cart). This is the server-side counterpart of the browser SDK's ReachBell.tag().
Body fields:
token— the subscriber's push token. Required.tags— a non-empty array of strings. Each tag is trimmed; empties are dropped, duplicates collapsed, and the set is capped at 50 tags per subscriber (each ≤ 100 chars).
curl -X POST https://api.reachbell.com/subscribers/tag \
-H "x-api-key: rb_live_yourkey" \
-H "Content-Type: application/json" \
-d '{
"token": "tok_01HX3...",
"tags": ["premium", "newsletter"]
}'Success — updated is true when at least one new tag was added, false when every tag was already present (additive $addToSet, so repeats never duplicate):
{
"success": true,
"updated": true
}Reasons when success is false: token_and_tags_required (missing token or empty tags), no_valid_tags (every supplied tag was blank after trimming), or subscriber_not_found (no subscriber with that token on the project, returned 200 OK).
Tags are additive — calling tag again appends. Use untag below to remove them.
Untag a subscriber
POST /subscribers/untag
Remove one or more tags from a subscriber. Same body shape as tag — { token, tags } — and the same response shape. Tags not currently on the subscriber are ignored.
curl -X POST https://api.reachbell.com/subscribers/untag \
-H "x-api-key: rb_live_yourkey" \
-H "Content-Type: application/json" \
-d '{
"token": "tok_01HX3...",
"tags": ["newsletter"]
}'Success — updated is true when at least one tag was removed:
{
"success": true,
"updated": true
}Reasons mirror tag: token_and_tags_required, no_valid_tags, and subscriber_not_found.
List subscribers for a project
GET /subscribers/project/:projectId
Returns a paginated list of subscribers. Supports filters via query string.
curl "https://api.reachbell.com/subscribers/project/proj_01HX2...?page=1&pageSize=50&platform=desktop&country=US" \
-H "x-api-key: rb_live_yourkey"Common filters: platform, country, browser, os, tag, status, createdAfter, createdBefore.
Success:
{
"data": [
{
"id": "sub_01HX3...",
"token": "tok_01HX3...",
"externalId": "user_42",
"platform": "desktop",
"country": "US",
"city": "San Francisco",
"tags": ["premium"],
"status": "active",
"createdAt": "2026-05-21T12:00:00.000Z"
}
],
"page": 1,
"pageSize": 50,
"total": 14823
}Errors:
{
"statusCode": 404,
"message": "Project not found",
"requestId": "req_01HX4..."
}Subscriber analytics
GET /subscribers/analytics/project/:projectId
Returns a breakdown of your subscriber base by country, platform, browser, OS, device, and city. The dashboard's audience charts are powered by this endpoint.
curl https://api.reachbell.com/subscribers/analytics/project/proj_01HX2... \
-H "x-api-key: rb_live_yourkey"Success:
{
"country": [
{ "key": "US", "count": 8412 },
{ "key": "DE", "count": 1903 }
],
"platform": [
{ "key": "desktop", "count": 9100 },
{ "key": "mobile", "count": 5723 }
],
"browser": [
{ "key": "Chrome", "count": 11402 },
{ "key": "Safari", "count": 2412 }
],
"os": [
{ "key": "macOS", "count": 4982 },
{ "key": "Windows", "count": 4118 }
],
"device": [
{ "key": "iPhone", "count": 3812 },
{ "key": "Pixel", "count": 1241 }
],
"city": [
{ "key": "San Francisco", "count": 1203 },
{ "key": "Berlin", "count": 892 }
]
}Analytics are computed live from the subscribers table — there's no aggregation delay, but on very large projects (>500k subscribers) the request can take a second or two. Cache the response if you're rendering it on a public page.
What's next?
- Send a one-off message with the Transactional API.
- Build a broadcast from the dashboard.
- Group subscribers with Segments before targeting.