Worked push samples
The operation list below documents each endpoint with its parameters, but an integrator runs one chain: register a device token → send a push → read the delivery receipt → retire the token. These samples render that chain as ordered calls with the real response envelopes you branch on. The end-to-end guide is Push integration — client SDK setup is out of scope here; this section adds the per-operation worked bodies the operations list below doesn’t carry. Copy a request as written, substitute your own ids, and compare the response envelope. Every sample shows cURL and TypeScript, matching this page’s language note. Errors follow Devotel Orbit’s{ error, meta } envelope.
1. Register the device
A device becomes reachable once it registers a token against an Orbit user. Re-registering the same token upserts (idempotent), and passingapp_install_id retires the previous token on a reinstall.
cURL
TypeScript
201
id — the send below targets devices by it. For
platform: "web", supply the subscription envelope
(PushSubscription.toJSON()) instead of token. Once your first token
lands, fire POST /api/v1/push/test-send to validate the provider
credential chain against the calling user’s own devices.
2. Send a push
POST /api/v1/push/send takes one of two targeting shapes:
device_token_ids (specific devices, as here) or user_ids (everywhere a
user registered; ["*"] broadcasts to every opted-in device, capped at
100,000 deliverable tokens). An immediate send returns 201; read
notifications[] per device — a top-level success does not mean every
device accepted.
cURL
TypeScript
201
status is sent (provider accepted), failed (provider
rejected — read the error string; permanent failures retire the token), or
skipped (frequency cap or suppression gated it). Supply a future
send_at (ISO-8601) to hold the send for a later instant; a scheduled send
answers 202 instead, and GET /api/v1/push/scheduled/{id} reads the
queued row back until DELETE cancels it while still scheduled (409
once it has left).
3. Read send status
GET /api/v1/push/notifications is the delivery log: every per-device row
carries its status, the delivered_at / opened_at receipts the client
SDK acks, and any provider error. Poll it (or listen for the
push.delivered / push.opened webhooks) to reconcile the send above to a
delivered / failed / skipped breakdown.
cURL
TypeScript
200
?cursor=<last-row-created_at>&limit=<page-size> when it
grows past one screen. A skipped row never leaves the send-time status —
no receipt arrives for a gated device.
4. Errors
Two error classes show up on every integration — read theissues list on
422s and map 404s on ids your tenant has never seen.
422 — malformed registration. A device registers with an unknown
platform (must be ios, android, huawei, or web), or with no
token / subscription for the platform it claims:
device_token_ids nor
user_ids supplied. A send that resolves to zero deliverable tokens answers
NO_DEVICE_TOKENS (422), and user_ids: ["*"] over the recipient cap
answers BROADCAST_TOO_LARGE (422).
404 — unknown device id. A token id not known to this tenant — a deleted
record, a typo, or another tenant’s id:
DELETE /api/v1/push/device-tokens/{id} on logout retires the current one.
5. Categories, scheduled sends and wake telemetry
Three smaller surfaces round out the chain above: notification categories (the action-button sets clients render), scheduled sends you queued withsend_at, and the device wake telemetry your client reports
after each push wake.
Read one category.
GET /api/v1/push/categories/{id}identifier your client
matches on, the optional sound and its action buttons.
POST /api/v1/push/categories echoes the stored
record with 201. identifier must be UPPER_SNAKE_CASE and is immutable
after create because clients cache it; to change one, delete and
recreate. A marketing category typically carries actions; a
transactional one often needs neither actions nor a sound.
GET /api/v1/push/scheduled/{id}send_at before the scheduler dispatches it.
DELETE /api/v1/push/scheduled/{id} is
a guarded status change, not a hard delete: only a row still in
scheduled status is cancelled. It answers 200 with the id and its new
status, 404 for an unknown id and 409 once the push has left
scheduled (sent or failed).
POST /api/v1/push/telemetry after a push wake with battery level, wake
count and delivery latency; the dashboard aggregates it so operators can
see the app is not draining devices. device_token_id and platform
("ios", "android", "web" or "huawei") are required and validated;
battery_pct may be omitted where the platform does not expose it (web),
and the authenticated user id is taken from the session, not from your
body. The 201 echoes the accepted report.
POST /api/v1/push/test-send fires one fixed-shape notification at every
enabled device owned by the calling operator and answers 200 with the
sent / failed counts plus one verdict per device. Your configuration
delivered everywhere when failed is 0 and sent is greater than
zero — that is the verdict the dashboard’s tester renders as success.
"tested": 2, "sent": 2, "failed": 0. Read the provider-level reason from the error string on
each failed verdict. A permanent provider rejection such as
BadDeviceToken also retires the dead token; register the device again
to retest it. With no enabled device registered to the calling user, the
endpoint answers NO_DEVICE_TOKENS (422) instead.
See also
- Push integration guide — the full lifecycle, client SDK setup, and the production credential checklist (explicitly out of scope here)
- Push Live Activities — ActivityKit start / update / end payloads
- Push channel page — provider credential setup for APNs, FCM, HMS, and VAPID
- Webhook event payloads —
push.delivered/push.openedwire shapes