> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Worked sequences

> Worked samples for the push lifecycle — register a device token, send with one of the three targeting shapes, ack engagement, and schedule a future send.

## Worked sequences

The operation list below documents each endpoint with its parameters, but the
push lifecycle — **register a device → send → ack engagement → schedule** —
spans several calls, and the response bodies are what you branch on. These
sequences show each call with the body it returns. The end-to-end workflow
guide is [Push integration](/guides/push-integration); 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. Errors follow Devotel Orbit's `{ error, meta }` envelope.

### Sequence 1 — register a device token

A device becomes reachable once it registers a token against an Orbit user.
The same token upserts on re-register (idempotent), and passing
`app_install_id` retires the previous token on a reinstall.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/push/device-tokens \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "user_8a1f2c",
    "platform": "ios",
    "token": "9f3a1c5d7b2e0486aab2f8c1d3e5b7a9c2d4e6f8a1b3c5d7e9f1b3d5f7a9c1e3f5",
    "app_install_id": "install_abc123"
  }'
```

```json 201 theme={null}
{
  "data": {
    "id": "deviceToken_7e2c1b",
    "user_id": "user_8a1f2c",
    "platform": "ios",
    "token": "9f3a1c5d7b2e0486aab2f8c1d3e5b7a9c2d4e6f8a1b3c5d7e9f1b3d5f7a9c1e3f5",
    "app_id": null,
    "app_install_id": "install_abc123",
    "device_id": null,
    "notification_channel": null
  },
  "meta": {
    "request_id": "req_push_reg",
    "timestamp": "2026-08-30T12:00:00.000Z"
  }
}
```

Save the returned `id` — you can target this device directly with
`device_token_ids` on send. For `platform: "web"`, supply the
`subscription` envelope (`PushSubscription.toJSON()`) instead of `token`.

**Node SDK escape hatch.** The call is the same body through
`orbit.request` when the typed SDK surface doesn't cover a field yet:

```typescript theme={null}
import { Orbit } from '@devotel-orbit/node'

const orbit = new Orbit({ apiKey: process.env.ORBIT_API_KEY! })

const { data } = await orbit.request('POST', '/push/device-tokens', {
  body: {
    user_id: 'user_8a1f2c',
    platform: 'ios',
    token: '9f3a1c5d…',
    app_install_id: 'install_abc123',
  },
})
// data.id is the Orbit device-token id.
```

Once your first token lands, fire `POST /api/v1/push/test-send` to validate
the whole provider credential chain against the calling user's own devices,
with no campaign stats pollution.

### Sequence 2 — send with targeting

`POST /api/v1/push/send` takes exactly one of two targeting shapes:
`device_token_ids` (specific devices) or `user_ids` (everywhere a user
registered). `user_ids: ["*"]` 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.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/push/send \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "user_ids": ["user_8a1f2c"],
    "title": "Your order is ready",
    "body": "Pickup is available at counter 4.",
    "data": { "order_id": "ord_91z" },
    "deep_link": "myapp://orders/ord_91z"
  }'
```

```json 201 theme={null}
{
  "data": {
    "notifications": [
      {
        "id": "pushNotif_2",
        "deviceTokenId": "deviceToken_7e2c1b",
        "transport": "apns",
        "status": "sent"
      },
      {
        "id": "pushNotif_3",
        "deviceTokenId": "deviceToken_8b44aa",
        "transport": "fcm",
        "status": "failed",
        "error": "BadDeviceToken"
      }
    ],
    "total": 2,
    "sent": 1,
    "devices_targeted": 2
  },
  "meta": {
    "request_id": "req_push_send",
    "timestamp": "2026-08-30T12:01:00.000Z"
  }
}
```

Per-device `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). Provider-reported
permanent failures retire the token automatically.

### Sequence 3 — ack engagement

The client SDK (or service worker) acks `delivered` when the device renders
the push and `opened` when the user taps it. The update is idempotent — the
matching `push.delivered` / `push.opened` webhook fires only on the first
genuine transition, so duplicate acks don't re-emit.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/push/notifications/pushNotif_2/ack \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "event": "opened", "at": "2026-08-30T12:02:10.000Z" }'
```

```json 200 theme={null}
{
  "data": {
    "id": "pushNotif_2",
    "event": "opened",
    "at": "2026-08-30T12:02:10.000Z"
  },
  "meta": {
    "request_id": "req_push_ack",
    "timestamp": "2026-08-30T12:02:10.000Z"
  }
}
```

### Sequence 4 — schedule a future send

Supply `send_at` (ISO-8601 with offset) on `/push/send` to hold the payload
for a future instant. The request returns `202` with a scheduled-push id;
the queued row replays through the send path at send time, so targeting,
caps, and suppression gates run identically.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/push/send \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "user_ids": ["user_8a1f2c"],
    "title": "Renewal reminder",
    "body": "Your subscription renews tomorrow.",
    "send_at": "2026-09-01T09:00:00+02:00"
  }'
```

```json 202 theme={null}
{
  "data": {
    "id": "scheduledPush_01J9ZABCDXYZ",
    "status": "scheduled",
    "send_at": "2026-09-01T07:00:00.000Z"
  },
  "meta": {
    "request_id": "req_push_sched",
    "timestamp": "2026-08-30T12:05:00.000Z"
  }
}
```

Read the queued row back — the full payload, attempt count, and last error
are on it; cancel it with `DELETE /api/v1/push/scheduled/{id}` while it is
still `scheduled` (returns `409` once the send has left):

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/push/scheduled/scheduledPush_01J9ZABCDXYZ \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

```json 200 theme={null}
{
  "data": {
    "id": "scheduledPush_01J9ZABCDXYZ",
    "payload": {
      "title": "Renewal reminder",
      "body": "Your subscription renews tomorrow.",
      "user_ids": ["user_8a1f2c"]
    },
    "send_at": "2026-09-01T07:00:00.000Z",
    "status": "scheduled",
    "attempts": 0,
    "last_error": null,
    "sent_at": null,
    "created_by": "user_admin_1",
    "created_at": "2026-08-30T12:05:00.000Z",
    "updated_at": "2026-08-30T12:05:00.000Z"
  },
  "meta": {
    "request_id": "req_push_sched_get",
    "timestamp": "2026-08-30T12:05:01.000Z"
  }
}
```

**Errors to handle.** These are the tenant-owned controls you configure —
run broadcasts under the deliverable-cap 422 and handle the pre-send gates:

| Code                    | Status | Trigger                                                                           |
| ----------------------- | ------ | --------------------------------------------------------------------------------- |
| `VALIDATION_ERROR`      | 422    | Neither `device_token_ids` nor `user_ids` supplied, or a field fails schema       |
| `NO_DEVICE_TOKENS`      | 422    | Target resolves to zero deliverable tokens after suppression / disabled filtering |
| `BROADCAST_TOO_LARGE`   | 422    | `user_ids: ["*"]` would exceed the 100,000-recipient broadcast cap                |
| `INVALID_PUSH_ENDPOINT` | 422    | On registration — the web-push `subscription.endpoint` is not publicly reachable  |
| `NOT_FOUND`             | 404    | Device token / scheduled push / notification id is unknown to this tenant         |

### See also

* [Push integration guide](/guides/push-integration) — the full lifecycle,
  SDK registration, and the production credential checklist
* [Push channel page](/channels/push) — provider credential setup for APNs,
  FCM, HMS, and VAPID
* [Webhook event payloads](/webhooks/event-payloads) — `push.delivered` /
  `push.opened` wire shapes
