> ## 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.

# Push device tokens and scheduled sends: operate the queue

> Run the two push operations satellites — inspect and prune registered device tokens, and review or cancel queued scheduled pushes — from the dashboard or the API.

# Push device tokens and scheduled sends

Two management surfaces under **Messages → Push → Manage** cover push operations after integration: the **Device Tokens** page (the registered-token inventory for your tenant) and the **Scheduled pushes** page (the queue of sends waiting for their scheduled time). Both are available to members with the owner, admin, or developer role. This guide covers what each page lists, how tokens move through their lifecycle, how the scheduler dispatches queued sends, and the matching API calls with runnable examples.

Set up registration and sending first with the [push integration guide](/guides/push-integration); curated notification categories are covered in [push notification categories](/guides/push-notification-categories). This page is about operating what those flows create.

## 1. Inspect the token inventory

The Device Tokens page lists every token registered in your tenant, newest first. Each row carries:

* **Platform** — `ios`, `android`, `huawei`, or `web`.
* **Owning user** — the `user_id` the token is scoped to, so you can tell which customer the device belongs to.
* **Install identity** — `app_install_id` and `device_id` when the SDK supplied them, which lets you spot duplicate rows from reinstalls.
* **Timestamps** — `created_at` (first registration) and `last_seen_at` (latest re-registration).
* **Delivery state** — the `enabled` flag. A disabled token stays on file but is skipped by every send.

The same inventory is available over the API, filtered to one user when you pass `user_id`:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/push/device-tokens?user_id=user_8a1f2c" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

```json theme={null}
{
  "data": [
    {
      "id": "deviceToken_01J9ZABCDEF1",
      "user_id": "user_8a1f2c",
      "platform": "ios",
      "app_install_id": "install_abc123",
      "created_at": "2026-08-01T09:14:00Z",
      "last_seen_at": "2026-09-02T07:41:00Z",
      "enabled": true
    }
  ]
}
```

The list is capped at 200 rows per call, newest first — use the `user_id` filter when a tenant holds more devices than that.

## 2. Token lifecycle: arrival, staleness, revocation

**Arrival.** A token appears when a client registers it with `POST /api/v1/push/device-tokens` — usually from the SDK at first run and again on every OS token refresh. Registration is idempotent: re-registering the same token refreshes `last_seen_at` instead of creating a duplicate. When the client passes a stable `app_install_id` and the OS rotates the token on reinstall, the install's previous row is retired automatically, so one device never keeps two live tokens.

**Staleness.** A token goes stale when the app is uninstalled, the user revokes notification permission, or the OS replaces the token and the client never re-registers. FCM, APNs, and Web Push report this back as delivery feedback — an `Unregistered`, `BadDeviceToken`, or `410 Gone` style rejection. Orbit treats those provider responses as permanent: the failing token is deleted from your inventory at send time, so the next send does not retry a dead device. Tokens that merely stop re-registering show their age through `last_seen_at` — a token untouched for weeks while the user stays active elsewhere is a candidate for cleanup.

**Revocation.** Two controls, depending on intent:

```bash theme={null}
# Pause delivery without deleting (the token can be re-enabled later)
curl -X PATCH https://api.orbit.devotel.io/api/v1/push/device-tokens/deviceToken_01J9ZABCDEF1 \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false }'

# Retire the token permanently
curl -X DELETE https://api.orbit.devotel.io/api/v1/push/device-tokens/deviceToken_01J9ZABCDEF1 \
  -H "X-API-Key: $ORBIT_API_KEY"
```

Disable when a user signs out or a device misbehaves and you may want it back; delete when the token is unrecoverable or you are pruning. Deletes return `204` on success and `404` when the id is unknown.

## 3. Scheduled pushes: review and cancel the queue

Every scheduled push is one-off: you enqueue it by supplying a future `send_at` (ISO-8601 with offset) on `POST /api/v1/push/send`, and it returns `202` with the scheduled push id instead of dispatching immediately. To model a recurring send — a weekly digest, a daily reminder — enqueue each occurrence as its own scheduled row from your own cadence (for example, a campaign or journey step); each appears in the queue individually and can be reviewed or cancelled on its own.

The dispatch sweep claims due rows roughly once a minute and replays the stored payload through the same send path an immediate send takes — so targeting, suppression, and frequency caps apply identically. A failed dispatch retries with exponential backoff; after five attempts the row is marked `failed` with the last recorded error, so you can inspect what went wrong instead of the send firing forever.

The Scheduled pushes page lists the queue, soonest `send_at` first. Filter it by status over the API — `scheduled`, `sent`, `cancelled`, or `failed`:

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

```json theme={null}
{
  "data": [
    {
      "id": "scheduledPush_01J9ZABCDXYZ",
      "payload": {
        "title": "Order shipped",
        "body": "Your order is on its way",
        "user_ids": ["user_8a1f2c"]
      },
      "send_at": "2026-09-10T09:00:00Z",
      "status": "scheduled",
      "attempts": 0,
      "last_error": null
    }
  ]
}
```

Read a single row — including its full payload, attempt count, and last error — before it dispatches, or to see why one failed:

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

Cancel a row while it is still `scheduled`. Cancellation is a guarded status change, not a hard delete — once the dispatch sweep has moved the row to `sent` or `failed`, the cancel returns `409 SCHEDULED_PUSH_NOT_CANCELLABLE` rather than silently succeeding:

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

## 4. API surface summary

Token operations:

| Method and path                         | Purpose                                                                   |
| --------------------------------------- | ------------------------------------------------------------------------- |
| `GET /api/v1/push/device-tokens`        | List tokens, newest first; optional `user_id` filter; 200-row cap.        |
| `POST /api/v1/push/device-tokens`       | Register or refresh a token (`/api/v1/push/register` is the older alias). |
| `PATCH /api/v1/push/device-tokens/:id`  | Set `enabled` true or false to pause or resume delivery.                  |
| `DELETE /api/v1/push/device-tokens/:id` | Revoke the token permanently.                                             |

Scheduled-send operations:

| Method and path                         | Purpose                                                         |
| --------------------------------------- | --------------------------------------------------------------- |
| `POST /api/v1/push/send` with `send_at` | Enqueue the send; returns `202` with the scheduled push id.     |
| `GET /api/v1/push/scheduled`            | List queued sends; optional `status` filter; 200-row cap.       |
| `GET /api/v1/push/scheduled/:id`        | Read one scheduled push with payload, attempts, and last error. |
| `DELETE /api/v1/push/scheduled/:id`     | Cancel while still `scheduled`; `409` once it has dispatched.   |

Writes (register, enable/disable, delete, enqueue, cancel) require the owner, admin, or developer role — the same gate the dashboard pages apply.

## 5. Best practices

* **Prune before big sends.** Broadcasts are capped at 100,000 deliverable tokens and per-device failures count against your deliverability. Delete tokens the providers have flagged dead and disable tokens from churned installs before a campaign — the broadcast only counts enabled, unsuppressed tokens, so cleanup directly protects headroom.
* **Register with `app_install_id`.** A stable per-install id is what lets a reinstall retire the old token instead of doubling the inventory. The [push integration guide](/guides/push-integration) covers the registration call.
* **Re-register on every OS token refresh.** That refresh stamps `last_seen_at`, which is also your signal for which rows have gone quiet.
* **Read the error before retrying a failed scheduled push.** The `last_error` and attempt count on the row tell you whether the failure is a bad target (fix the payload and enqueue a new send) or a transient provider blip (cancel and re-enqueue if you no longer want it).
* **Cancel rather than let a stale announcement fire.** If a queued promotion or announcement no longer makes sense when its time arrives, delete it while its status is still `scheduled`.
* **Curate the send shape, not just the queue.** When your scheduled sends reference a named category for sounds, actions, or the Android channel, manage those centrally as in the [push notification categories guide](/guides/push-notification-categories).
