Device Registration
Register and deregister devices with the framework-agnostic endpoint — deviceId, platform, APNs/FCM tokens, subscriber ids, and the exact rules and error codes.
Registration tells Native Notify "this device can receive pushes, on this token". Call it on every app launch (and whenever the push token changes) — it is an idempotent upsert, so repeats are cheap and never duplicate a device.
POST https://app.nativenotify.com/api/universal/device/register
Request
{
"appId": 123,
"appToken": "yourAppToken",
"deviceId": "A1B2C3D4-install-key",
"platform": "ios",
"subscriberId": "user_8241",
"appVersion": "2.4.0",
"timezone": "America/New_York",
"tokens": {
"apnsToken": "9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f009f8a7b6c5d4e3f2a1b0c9d8e"
}
}
| Field | Required | Notes |
|---|---|---|
appId, appToken | yes | Your app's pair from the dashboard. |
deviceId | yes | Your stable per-install key. Max 200 chars. This is the device's identity — choose it once and keep it. |
platform | yes | "ios" or "android". |
subscriberId | no | Send your user id to make it an indie device; omit (or empty) for a mass device. Max 256 chars. Numbers are accepted and coerced. |
appVersion | no | Trimmed, capped at 50 chars. Useful in support views. |
timezone | no | IANA zone, trimmed, capped at 60 chars. |
tokens.apnsToken | one of the two | iOS only. Hex device token, 64+ characters. Aliases: apns_token, apnToken. |
tokens.fcmToken | one of the two | Android only. FCM registration token, 32+ URL-safe characters. Alias: fcm_token. |
At least one token is required. Token values are capped at 400 characters, and Expo push tokens are rejected here (unsupported_token_type) — that is a different service; see Universal Push.
Response — 201
{
"ok": true,
"device": {
"appId": 123,
"deviceId": "A1B2C3D4-install-key",
"platform": "ios",
"subscriberId": "user_8241",
"appVersion": "2.4.0",
"timezone": "America/New_York",
"createdAt": "2026-09-20T14:02:11.000Z",
"lastSeenAt": "2026-09-23T09:10:44.000Z"
},
"tokens": { "apnsToken": "ok" }
}
tokens reports per token what happened: "ok" (already on this device), "refreshed" (value updated), or "reassigned" (the token moved here from another device).
Semantics that matter
- Re-registering never duplicates. The unique keys are
(app, deviceId)for devices and(app, token type, token)for tokens. A token only ever exists once per app. - First registration wins for ages.
createdAtis never bumped by a re-register;lastSeenAtrefreshes every time. - Token rotation is normal. When APNs/FCM hands you a new token, register again — same
deviceId, new token value. - A registration clears the token's dead flag. Presenting a token again is proof it is in current use. Failure strikes are stored per device — re-registering the same device keeps its history; a token that moves to a different device is treated as fresh (strikes reset).
- Omitted optional fields keep their stored value — sending
appVersionalone never wipes the subscriber id. - Platform pairing is enforced:
apnsTokenfrom aniosdevice,fcmTokenfrom anandroiddevice.
Deregister
POST https://app.nativenotify.com/api/universal/device/deregister
{ "appId": 123, "appToken": "yourAppToken", "deviceId": "A1B2C3D4-install-key" }
{ "ok": true, "deviceId": "A1B2C3D4-install-key", "removed": true, "tokensRemoved": 1 }
Deregister is a hard delete — the device row is removed and its tokens cascade. It is idempotent: an unknown deviceId still answers 200 with "removed": false, so a logout that runs twice (or a retried request) never fails. Call it when a user logs out of an indie identity, or when you want to stop pushes for that install.
Error codes
Errors use the stable envelope { "error": { "code", "message", "field"? } }:
| Code | Meaning |
|---|---|
missing_field | appId, deviceId, platform or both tokens are missing. |
invalid_platform | platform is not ios or android. |
invalid_device_id / invalid_subscriber_id | Too long or wrong type. |
no_tokens_provided | Neither tokens.apnsToken nor tokens.fcmToken was sent. |
invalid_token_format | Token does not match its shape (hex APNs / URL-safe FCM) or is over 400 chars. |
token_platform_mismatch | An APNs token from an Android device, or an FCM token from an iOS device. |
unsupported_token_type | An Expo push token was sent — use the universal tokens instead. |
app_not_found | The app id is unknown; a wrong appToken answers 401 like the rest of the API. |
Try it
Register, then immediately prove delivery with a test send to that deviceId, or check the app's counts through /api/universal/health.