> ## 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 notification categories: curated interactive templates for sends

> Define named APNs/FCM notification categories once, then reference them by identifier on POST /push/send — wire-bound identifiers, sounds, and action buttons, curated from the dashboard or the API.

# Push notification categories

A notification category is a per-tenant template you reference by identifier on a send — it carries an optional custom sound and up to 10 interactive action buttons so every send that names it expands to the same curated shape. Define the category once (dashboard or API), then pass its `identifier` as `notification_channel` on `POST /api/v1/push/send`.

This is a tenant-owned control: you curate which categories exist, and the send path expands them per request. Nothing here gates delivery — a send that names an unknown identifier proceeds with a plain, non-interactive notification.

## 1. Why curate categories

Push delivery fails or under-performs in predictable ways, and category hints address the content side:

* **Interactive actions ship uniformly.** Instead of passing per-platform overrides on every send, you name one identifier and the send path expands its stored iOS actions, Android tap action, and sound.
* **APNs payload rules are enforced at curation time.** Identifiers are validated as `UPPER_SNAKE_CASE` (max 64 chars), action titles are capped at 128 chars, and a category carries at most 10 actions — the constraints APNs enforces are caught when you author the category, not when APNs rejects a mis-shaped payload with a per-device `failed` result.
* **Identifiers are wire-bound.** The iOS / Android client registers and matches on the identifier string, so categories are immutable after create — delete and recreate to change one. This keeps in-flight pushes referencing the shape the client cached.

For the delivery-failure side (per-device `error` strings like `BadDeviceToken`, suppression, and frequency-cap skips), see the [push end-to-end guide](/guides/push-integration#3-handle-per-device-errors).

## 2. Manage categories in the dashboard

Open **Messages → Push → Manage push notifications → Notification Categories** (`/messages/push/manage/notification-categories`). The page lists the categories defined for your tenant and sits beside the Scheduled pushes, Device Tokens, and Live Activities satellites.

The page is guarded for `owner`, `admin`, and `developer` roles — the same role set the other push management pages require.

## 3. Category shape

Each category record holds:

| Field        | Constraint                                            | Use                                                                                                                                                                                                 |
| ------------ | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `identifier` | Uppercase snake case, max 64 chars, unique per tenant | The wire-bound name the client matches on and the send path resolves.                                                                                                                               |
| `name`       | 1–120 chars                                           | Operator-facing label.                                                                                                                                                                              |
| `sound`      | Optional, max 128 chars                               | Default custom sound applied unless the send overrides it.                                                                                                                                          |
| `actions`    | Up to 10                                              | Interactive buttons — each with an UPPER\_SNAKE\_CASE `identifier`, a `title` (max 128 chars), and optional options drawn from `foreground`, `destructive`, `authenticationRequired`, `background`. |

The API mirrors the shape exactly:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/push/categories \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Invoice reminders",
    "identifier": "INVOICE_REMINDER",
    "sound": "chime.aiff",
    "actions": [
      { "identifier": "PAY_NOW", "title": "Pay now", "options": ["foreground"] },
      { "identifier": "SNOOZE", "title": "Snooze", "options": ["background"] }
    ]
  }'
```

Supporting endpoints: `GET /api/v1/push/categories` (newest first, capped at 200 rows — also the per-tenant creation limit), `GET /api/v1/push/categories/:id`, and `DELETE /api/v1/push/categories/:id` (empty `204`). Identifier reuse is the supported update pattern: delete, then recreate — a conflicting identifier returns `409 PUSH_CATEGORY_IDENTIFIER_CONFLICT`.

## 4. Apply a category on send

Pass the category's `identifier` as `notification_channel` on `POST /api/v1/push/send`:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/push/send \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_ids": ["user_8a1f2c"],
    "title": "Invoice #1042 is due",
    "body": "Tap to review and pay.",
    "notification_channel": "INVOICE_REMINDER"
  }'
```

When the identifier resolves, the send expands the category before the provider call:

* **iOS** — the APNs payload carries `category: "INVOICE_REMINDER"`, plus the category's custom `sound` unless the send's own `ios.sound` override wins. If the send supplies no `actions` of its own, the category's stored actions fill in.
* **Android** — the FCM `click_action` is set to the identifier, and unless you override `android.channel_id` explicitly, `notification_channel` doubles as the Android channel id.
* **Unresolved identifier** — the send proceeds as a plain notification; naming an unknown identifier is not a failure.

Per-device results stay in the `notifications[]` array exactly as without categories: `status` of `sent`, `failed`, or `skipped`, with a provider `error` string (for example `BadDeviceToken`) on failures. Categories annotate delivery shape; the [push end-to-end guide](/guides/push-integration#3-handle-per-device-errors) covers the failure taxonomy.

## 5. End-to-end example

1. **Create the category** — with the `PAY_NOW` / `SNOOZE` actions shown above, or through the dashboard's Notification Categories page.
2. **Send a test push** — fire `POST /api/v1/push/test-send` with `"notification_channel": "INVOICE_REMINDER"` against your own devices to validate the whole chain without polluting campaign stats.
3. **Verify on device** — the iOS device renders the notification with the registered `PAY NOW` / `SNOOZE` buttons and the custom chime; Android routes the tap through `click_action: "INVOICE_REMINDER"`.
4. **Read the result** — each device in `notifications[]` reports `sent`; a `failed` row on an unregistered device still carries a raw provider error such as `BadDeviceToken` for you to act on.

Categories are per-tenant, and the per-tenant cap is 200 — within APNs' practical category limits per bundle.
