# Realtime API

The Realtime area publishes 28 REST endpoints. Paths are relative to `https://api.lessly.com`; see [Authentication](/reference/openapi#authentication) for the required header.

### `GET` `/realtime/archive`

Read a channel's durably archived history from Postgres (survives the Redis hot window). Offset-paginated: pass the returned next_offset as after_offset for the next page.

MCP tool: `realtime_archive_get`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| channel | query | string | • | Channel whose archived history to read |
| after_offset | query | string | — | Return entries strictly after this offset (from a previous page) |
| limit | query | integer | — | — |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| channel | string | • | — |
| entries | array | • | — |
| next_offset | string | • | Pass as after_offset to fetch the next page; null = no more entries |

### `POST` `/realtime/archive/exports`

Start an async NDJSON export of a channel's archived history to the product bucket. Returns an export id; poll realtime_archive_export_status until done.

MCP tool: `realtime_archive_export`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| channel | body | string | • | Channel whose archive to export |
| from_ts | body | integer | — | Only entries with ts >= from_ts (epoch ms) |
| to_ts | body | integer | — | Only entries with ts <= to_ts (epoch ms) |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| status | string | • | — |
| export_id | string | • | — |

### `GET` `/realtime/archive/exports/:export_id`

Check an archive export: status, and the NDJSON blob reference once done

MCP tool: `realtime_archive_export_status`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| export_id | path | string | • | Export id returned by realtime_archive_export |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| ref | object | • | Blob reference of the NDJSON export once status is "done" |
| error | string | • | — |
| status | string | • | — |
| export_id | string | • | — |
| entry_count | integer | • | — |

### `GET` `/realtime/grants`

List realtime channel grants for this product, optionally filtered by subject

MCP tool: `realtime_grant_list`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| subject | query | string | — | Filter grants by subject |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| grants | array | • | — |

### `POST` `/realtime/grants`

Grant a subject (identity id or "*" for all identities) operations on channels matching a pattern; "*" in a pattern matches exactly one segment

MCP tool: `realtime_grant_create`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| subject | body | string | • | Identity id the grant applies to, or "*" for every identity in the product |
| pattern | body | string | • | Channel pattern, e.g. "chat:*" ("*" matches exactly one segment) |
| ops | body | array | • | Operations granted on matching channels |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| ops | array | • | — |
| pattern | string | • | — |
| subject | string | • | — |
| createdAt | string | • | — |

### `DELETE` `/realtime/grants/:id`

Revoke a realtime channel grant by id

MCP tool: `realtime_grant_revoke`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| id | path | string | • | Grant id to revoke |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| revoked | boolean | • | — |

### `POST` `/realtime/messages`

Publish a JSON message to a realtime channel of the current product

MCP tool: `realtime_messages_publish`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| channel | body | string | • | Channel to publish the message to |
| data | body | string | • | JSON payload delivered to channel subscribers |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| epoch | string | — | Stream epoch of the stored message (present when history is on) |
| offset | string | — | Stream offset of the stored message (present when history is on) |
| channel | string | • | Channel the message was published to |
| published | boolean | • | Publish acknowledged by the fan-out backend |

### `GET` `/realtime/messages/history`

Read message history of a realtime channel: either a cursor (offset + epoch, replays strictly after the offset; recovered:false on epoch mismatch or aged-out entries) or a window (last_n / last_ms). Conceptually requires the `history` capability; in Phase 1 this surface is Identity-authenticated management access.

MCP tool: `realtime_history_get`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| channel | query | string | • | Channel to read history from |
| offset | query | string | — | Cursor: resume strictly after this offset (requires epoch) |
| epoch | query | string | — | Cursor: stream epoch the offset belongs to |
| last_n | query | integer | — | Window: return the last N entries |
| last_ms | query | integer | — | Window: return entries newer than now minus this many milliseconds |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| epoch | string | • | Current stream epoch, or null when the channel has no history yet |
| channel | string | • | Channel the history was read from |
| entries | array | • | Entries in stream order |
| recovered | boolean | • | false when the cursor epoch mismatches or entries aged out — client must resync |

### `GET` `/realtime/namespaces`

List all realtime namespaces registered for this product

MCP tool: `realtime_namespace_list`

**Parameters**

No parameters.

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| namespaces | array | • | — |

### `POST` `/realtime/namespaces`

Register a realtime namespace (the part of a channel name before the first colon) with its policy: visibility, presence, client events, history, encryption, identified-only

MCP tool: `realtime_namespace_create`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| name | body | string | • | Namespace name (the part of a channel name before the first colon) |
| visibility | body | string | — | public: any authenticated identity may subscribe without a grant |
| presence | body | boolean | — | Whether presence is enabled on channels in this namespace |
| clientEvents | body | boolean | — | Whether clients may publish events directly |
| history | body | string | — | History retention policy for channels in this namespace |
| historyWindowSeconds | body | integer | — | Retention window in seconds; required when history is "window" |
| encryptionRequired | body | boolean | — | Whether payloads must be end-to-end encrypted |
| identifiedOnly | body | boolean | — | Whether only identified (non-anonymous) identities get capabilities |
| subscribeProxyUrl | body | string | — | If set, token mint consults this URL per requested channel (fail-closed); null disables |
| archive | body | string | — | History archive policy: off, or durably retain archived entries for N days (requires history != "none") |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| name | string | • | — |
| archive | string | • | History archive policy |
| history | string | • | — |
| presence | boolean | • | — |
| createdAt | string | • | — |
| updatedAt | string | • | — |
| visibility | string | • | — |
| clientEvents | boolean | • | — |
| identifiedOnly | boolean | • | — |
| subscribeProxyUrl | string | • | — |
| encryptionRequired | boolean | • | — |
| historyWindowSeconds | integer | • | — |
| subscribeProxySecret | string | — | Full proxy signing secret — returned ONLY when subscribeProxyUrl is set/changed |
| subscribeProxySecretPrefix | string | • | Visible prefix of the proxy signing secret; null when no proxy url is set |

### `DELETE` `/realtime/namespaces/:name`

Delete a realtime namespace by name (grants on it stop resolving)

MCP tool: `realtime_namespace_delete`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| name | path | string | • | Namespace name |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| deleted | boolean | • | — |

### `GET` `/realtime/namespaces/:name`

Get a realtime namespace and its policy by name

MCP tool: `realtime_namespace_get`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| name | path | string | • | Namespace name |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| name | string | • | — |
| archive | string | • | History archive policy |
| history | string | • | — |
| presence | boolean | • | — |
| createdAt | string | • | — |
| updatedAt | string | • | — |
| visibility | string | • | — |
| clientEvents | boolean | • | — |
| identifiedOnly | boolean | • | — |
| subscribeProxyUrl | string | • | — |
| encryptionRequired | boolean | • | — |
| historyWindowSeconds | integer | • | — |
| subscribeProxySecret | string | — | Full proxy signing secret — returned ONLY when subscribeProxyUrl is set/changed |
| subscribeProxySecretPrefix | string | • | Visible prefix of the proxy signing secret; null when no proxy url is set |

### `PATCH` `/realtime/namespaces/:name`

Update the policy of a realtime namespace

MCP tool: `realtime_namespace_update`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| name | path | string | • | Namespace name to update |
| visibility | body | string | — | public: any authenticated identity may subscribe without a grant |
| presence | body | boolean | — | Whether presence is enabled on channels in this namespace |
| clientEvents | body | boolean | — | Whether clients may publish events directly |
| history | body | string | — | History retention policy for channels in this namespace |
| historyWindowSeconds | body | integer | — | Retention window in seconds; required when history is "window" |
| encryptionRequired | body | boolean | — | Whether payloads must be end-to-end encrypted |
| identifiedOnly | body | boolean | — | Whether only identified (non-anonymous) identities get capabilities |
| subscribeProxyUrl | body | string | — | If set, token mint consults this URL per requested channel (fail-closed); null disables |
| archive | body | string | — | History archive policy: off, or durably retain archived entries for N days (requires history != "none") |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| name | string | • | — |
| archive | string | • | History archive policy |
| history | string | • | — |
| presence | boolean | • | — |
| createdAt | string | • | — |
| updatedAt | string | • | — |
| visibility | string | • | — |
| clientEvents | boolean | • | — |
| identifiedOnly | boolean | • | — |
| subscribeProxyUrl | string | • | — |
| encryptionRequired | boolean | • | — |
| historyWindowSeconds | integer | • | — |
| subscribeProxySecret | string | — | Full proxy signing secret — returned ONLY when subscribeProxyUrl is set/changed |
| subscribeProxySecretPrefix | string | • | Visible prefix of the proxy signing secret; null when no proxy url is set |

### `GET` `/realtime/presence`

Get the current presence roster for a channel. Roster is deduplicated by identity (connections counts live sockets); a per-connection view is deferred — the store is identity-keyed.

MCP tool: `realtime_presence_get`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| channel | query | string | • | Channel name, "namespace:rest" |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| channel | string | • | Channel name |
| members | array | • | Current roster, deduplicated by identity |

### `POST` `/realtime/presence/enter`

Enter presence on a realtime channel; upserts the caller (or member_id) into the roster

MCP tool: `realtime_presence_enter`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| channel | body | string | • | Channel name, "namespace:rest" |
| info | body | string | — | Arbitrary JSON metadata for the member (<=10KB serialized) |
| ttl_seconds | body | integer | — | Presence entry TTL in seconds (1-3600) |
| member_id | body | string | — | Act for a synthetic member id instead of the caller identity (backend integrations) |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| member | object | • | The entered/updated member |
| channel | string | • | Channel name |

### `POST` `/realtime/presence/leave`

Leave presence on a realtime channel, removing the caller (or member_id) from the roster

MCP tool: `realtime_presence_leave`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| channel | body | string | • | Channel name, "namespace:rest" |
| member_id | body | string | — | Act for a synthetic member id instead of the caller identity (backend integrations) |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| channel | string | • | Channel name |
| removed | boolean | • | Whether the member was removed |

### `GET` `/realtime/presence/stats`

Get the presence member count for a channel

MCP tool: `realtime_presence_stats`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| channel | query | string | • | Channel name, "namespace:rest" |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| channel | string | • | Channel name |
| members | integer | • | Number of distinct identities present |

### `POST` `/realtime/presence/update`

Update a presence member's info and/or refresh its TTL

MCP tool: `realtime_presence_update`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| channel | body | string | • | Channel name, "namespace:rest" |
| info | body | string | — | Arbitrary JSON metadata for the member (<=10KB serialized) |
| ttl_seconds | body | integer | — | Presence entry TTL in seconds (1-3600) |
| member_id | body | string | — | Act for a synthetic member id instead of the caller identity (backend integrations) |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| member | object | • | The entered/updated member |
| channel | string | • | Channel name |

### `GET` `/realtime/public-access/status`

Check whether this product has a public key configured — an onboarding hint for the portal, never an authorization decision

MCP tool: `realtime_public_access_status`

**Parameters**

No parameters.

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| exists | boolean | • | Whether this product has created a public key yet (an onboarding hint, not a gate) |

### `GET` `/realtime/status`

Get realtime service status: Redis fan-out connectivity, token signing key presence, gateway URL

MCP tool: `realtime_status_get`

**Parameters**

No parameters.

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| redis | string | • | Redis fan-out connectivity |
| service | string | • | API liveness marker |
| gatewayUrl | string | • | WebSocket URL of the realtime gateway |
| signingKeyConfigured | boolean | • | Whether the token signing key is present |

### `POST` `/realtime/tokens`

Mint a short-lived capability token (JWT) carrying the capabilities the identity has via namespace policy and grants (optionally filtered to the given channels), plus the gateway WebSocket URL to connect to

MCP tool: `realtime_tokens_create`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| channels | body | array | — | Concrete channels to filter the resolved capabilities to; omit to receive all capabilities the identity has |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| token | string | • | Signed EdDSA capability JWT (60s TTL) |
| gatewayUrl | string | • | WebSocket URL of the realtime gateway to connect to |

### `GET` `/realtime/webhooks`

List all client webhooks registered for this product (secret metadata only)

MCP tool: `realtime_webhook_list`

**Parameters**

No parameters.

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| webhooks | array | • | — |

### `POST` `/realtime/webhooks`

Register a client webhook: lifecycle notifications (channel occupied/vacated, presence member added/removed) are POSTed to the URL signed with HMAC-SHA256. Returns the full signing secret ONCE.

MCP tool: `realtime_webhook_create`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| url | body | string | • | Endpoint that receives signed lifecycle POSTs (https in production) |
| events | body | array | • | Lifecycle event types this webhook receives |
| description | body | string | — | Free-form note |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| secret | string | • | Full signing secret — returned only once, at creation. Store it now. |
| webhook | object | • | — |

### `DELETE` `/realtime/webhooks/:webhookId`

Delete a client webhook (its secrets and delivery log go with it)

MCP tool: `realtime_webhook_delete`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| webhookId | path | string | • | Webhook id |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| deleted | boolean | • | — |

### `GET` `/realtime/webhooks/:webhookId`

Get a client webhook by id (secret metadata only, never the secret)

MCP tool: `realtime_webhook_get`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| webhookId | path | string | • | Webhook id |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| url | string | • | — |
| active | boolean | • | — |
| events | array | • | — |
| secrets | array | • | Secret metadata only — never the secret itself |
| createdAt | string | • | — |
| updatedAt | string | • | — |
| description | string | • | — |

### `PATCH` `/realtime/webhooks/:webhookId`

Update a client webhook: url, event filter, description, active flag

MCP tool: `realtime_webhook_update`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| url | body | string | — | Endpoint that receives signed lifecycle POSTs (https in production) |
| events | body | array | — | Lifecycle event types this webhook receives |
| description | body | string | — | Free-form note |
| active | body | boolean | — | Inactive webhooks receive no deliveries |
| webhookId | path | string | • | Webhook id to update |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| url | string | • | — |
| active | boolean | • | — |
| events | array | • | — |
| secrets | array | • | Secret metadata only — never the secret itself |
| createdAt | string | • | — |
| updatedAt | string | • | — |
| description | string | • | — |

### `GET` `/realtime/webhooks/:webhookId/deliveries`

List recent delivery attempts of a webhook (status, attempts, response code)

MCP tool: `realtime_webhook_deliveries_list`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| webhookId | path | string | • | Webhook id |
| limit | query | integer | — | Max deliveries to return (newest first) |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| deliveries | array | • | — |

### `POST` `/realtime/webhooks/:webhookId/secret/rotate`

Rotate the webhook signing secret with an overlap window: the new secret signs immediately (returned ONCE), the previous one keeps verifying until its grace expiry

MCP tool: `realtime_webhook_secret_rotate`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| webhookId | path | string | • | Webhook id |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| prefix | string | • | Visible prefix of the new secret (X-Realtime-Key) |
| secret | string | • | New full signing secret — returned only once. Store it now. |
| previousExpiresAt | string | • | When the rotated-out secret stops verifying (grace window) |
