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

# Notifications API

> List, read, and dismiss in-app notifications, and stream new ones live over Server-Sent Events.

# Notifications API

The Notifications API backs the dashboard bell: a per-user feed of in-app alerts —
delivery failures, billing events, security sign-ins, port-status changes, agent
handoffs, SLA breaches, and more. Read the feed with the list endpoints, mark rows
read or dismiss them, and subscribe to `GET /api/v1/notifications/stream` to receive
new notifications in real time.

**Base path:** `/api/v1/notifications`

**Authentication:** session JWT. Notifications are scoped to the signed-in user and
their tenant, so these endpoints use your dashboard session rather than a workspace
API key. The live stream supports a query-parameter token so browsers can
authenticate `EventSource` connections (see [Live stream](#live-stream-sse)).

| Method   | Path                                 | Purpose                                                |
| -------- | ------------------------------------ | ------------------------------------------------------ |
| `GET`    | `/api/v1/notifications/`             | List your notifications (cursor-paginated, filterable) |
| `GET`    | `/api/v1/notifications/unread-count` | Unread count for the bell badge                        |
| `PUT`    | `/api/v1/notifications/{id}/read`    | Mark a single notification read                        |
| `POST`   | `/api/v1/notifications/read-all`     | Mark every unread notification read                    |
| `DELETE` | `/api/v1/notifications/{id}`         | Dismiss (delete) one of your notifications             |
| `POST`   | `/api/v1/notifications/sse-token`    | Mint a short-lived one-time token for the stream       |
| `GET`    | `/api/v1/notifications/stream`       | Subscribe to new notifications over SSE                |

## The notification object

```json theme={null}
{
  "id": "notif_0a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "user_id": "user_abc",
  "org_id": "org_xyz",
  "kind": "payment_failed",
  "severity": "error",
  "title": "Payment failed",
  "body": "Your last top-up was declined.",
  "action_url": "/billing",
  "read_at": null,
  "created_at": "2026-06-30T12:00:00.000Z",
  "expires_at": null,
  "source_pillar": "system",
  "category": "billing"
}
```

* `severity` is one of `info`, `warning`, `error` — it drives the icon colour.
* `read_at` is `null` until the notification is marked read.
* `source_pillar` and `category` are coarse facets used by the dashboard filter
  chips. `source_pillar` is one of `cpaas`, `ucaas`, `ccaas`, `aiaas`, `cxaas`,
  `cspaas`, `naas`, `verify`, `system`. `category` is one of `sla_breach`,
  `missed_call`, `campaign_result`, `agent_state`, `verification`,
  `webhook_failure`, `billing`, `security`, `compliance`, `system`.
* Notifications past their `expires_at` are filtered out of the list automatically.

## List notifications — `GET /api/v1/notifications/`

Returns your notifications, newest first, in a cursor-paginated page.

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/notifications?limit=20&unread_only=true" \
  -H "Authorization: Bearer <session-jwt>"
```

**Query parameters**

| Parameter       | Type    | Default | Notes                                                         |
| --------------- | ------- | ------- | ------------------------------------------------------------- |
| `limit`         | integer | `20`    | Page size, `1`–`100`.                                         |
| `cursor`        | string  | —       | A notification `id`; the page starts strictly after that row. |
| `unread_only`   | boolean | —       | When `true`, returns only unread rows.                        |
| `kind`          | string  | —       | Filter by notification kind (e.g. `payment_failed`).          |
| `severity`      | string  | —       | One of `info`, `warning`, `error`.                            |
| `source_pillar` | string  | —       | One of the pillar values above.                               |
| `category`      | string  | —       | One of the category values above.                             |

```json theme={null}
{
  "data": {
    "data": [ /* notification objects */ ],
    "pagination": { "cursor": "notif_…", "has_more": true }
  },
  "meta": { "request_id": "req_…", "timestamp": "2026-06-30T12:00:00.000Z" }
}
```

To fetch the next page, pass `pagination.cursor` back as the `cursor` query
parameter. When `has_more` is `false`, `cursor` is `null` and you have reached the
end of the feed.

## Unread count — `GET /api/v1/notifications/unread-count`

Returns `{ "count": <n> }` — the number backing the bell badge.

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/notifications/unread-count" \
  -H "Authorization: Bearer <session-jwt>"
```

## Mark read — `PUT /api/v1/notifications/{id}/read`

Marks a single notification read and returns the updated row. Returns `404` if the
id does not exist or is not yours.

```bash theme={null}
curl -X PUT \
  "https://api.orbit.devotel.io/api/v1/notifications/notif_0a1b…/read" \
  -H "Authorization: Bearer <session-jwt>"
```

## Mark all read — `POST /api/v1/notifications/read-all`

Marks every unread notification read in one call. Returns
`{ "affected": <n> }` — the number of rows updated.

```bash theme={null}
curl -X POST \
  "https://api.orbit.devotel.io/api/v1/notifications/read-all" \
  -H "Authorization: Bearer <session-jwt>"
```

## Dismiss — `DELETE /api/v1/notifications/{id}`

Deletes one of your notifications and returns `{ "deleted": "<id>" }`. Returns `404`
if the id does not exist or is not user-scoped.

```bash theme={null}
curl -X DELETE \
  "https://api.orbit.devotel.io/api/v1/notifications/notif_0a1b…" \
  -H "Authorization: Bearer <session-jwt>"
```

<Note>
  Organization-wide notifications are shared across the workspace and cannot be
  dismissed by an individual user — they clear on their own when they expire.
  Dismiss applies only to your own user-scoped rows.
</Note>

## Live stream — SSE

`GET /api/v1/notifications/stream` pushes new notifications to the browser in real
time over [Server-Sent Events](https://developer.mozilla.org/docs/Web/API/Server-sent_events).
Browsers cannot set request headers on an `EventSource`, so the stream accepts the
session token as a query parameter. Two authentication paths are supported:

* **One-time token (preferred).** Call `POST /api/v1/notifications/sse-token` to mint
  a short-lived token, then open the stream with `?ot=<token>`. This keeps your
  long-lived session JWT out of server access logs and browser history.
* **Legacy JWT (fallback).** Open the stream with `?token=<session-jwt>` directly.
  Use this only when a one-time token is unavailable.

### Mint a one-time token — `POST /api/v1/notifications/sse-token`

```bash theme={null}
curl -X POST \
  "https://api.orbit.devotel.io/api/v1/notifications/sse-token" \
  -H "Authorization: Bearer <session-jwt>"
```

On success returns `201` with the token and its lifetime:

```json theme={null}
{
  "data": { "ot": "sset_…", "expires_in": 90 },
  "meta": { "request_id": "req_…", "timestamp": "2026-06-30T12:00:00.000Z" }
}
```

The token is valid for **90 seconds** and is meant to be redeemed once, immediately,
by opening the stream. Mint a fresh token each time you (re)connect.

If the token store is briefly unavailable the endpoint **degrades gracefully**: it
returns `200` with `{ "ot": null, "fallback": "token" }` instead of an error. When
you see `ot: null`, fall back to opening the stream with `?token=<session-jwt>`.

### Open the stream — `GET /api/v1/notifications/stream`

```bash theme={null}
curl -N \
  "https://api.orbit.devotel.io/api/v1/notifications/stream?ot=sset_your_one_time_token"
```

The stream opens with a `connected` event, then emits one event per new
notification. The event name reflects the notification type (for example
`notification.created`); the `data:` line carries the notification JSON.

```text theme={null}
event: connected
data: {"type":"connected","data":{"userId":"user_abc"}}

event: notification.created
data: {"id":"notif_…","kind":"payment_failed","severity":"error", ...}
```

A `: heartbeat` comment is sent every 30 seconds to keep the connection alive. If
the stream ends, reconnect — mint a fresh one-time token first if you are using the
`?ot=` path.

<Warning>
  **Concurrency cap — 50 streams per tenant.** Each tenant may hold at most **50
  concurrent SSE connections** to this endpoint across the whole platform. Opening
  another stream over the cap is rejected with `429` and `error.code` =
  `TOO_MANY_CONNECTIONS`. Close streams you no longer need (each open browser tab
  holds one), and reuse a single connection per tab rather than opening one per
  component.
</Warning>

The stream is for browser clients: connections must come from a trusted dashboard
origin with a browser `User-Agent`. Non-browser clients are rejected — to consume
the same events from a server, use the durable [Webhooks](/webhooks/overview) push
instead.

## See also

* [Webhooks](/webhooks/overview) — durable, retry-handled push for server-side consumers.
* [Events API](/api-reference/events) — the platform-wide live event firehose.
