# Realtime

Lessly Realtime moves messages between your backend and your users' open browser tabs. Your backend publishes to a named channel; every client currently subscribed to that channel receives the message within the same request. On top of that it keeps a roster of who is on a channel, and a replayable history of what was sent there.

You work with your own concepts: namespaces, channels, capability tokens and grants. Holding the connections open, delivering to every subscriber and reconnecting a client that drops off are Realtime's job.

## When to reach for it

Reach for Realtime when something that happens on your server has to show up in a browser without the browser asking for it: a chat message, a document another person is editing, an order changing state, a long job reporting progress, a dashboard that must stay current. Reach for [presence](/ship/realtime/presence) when you need to show who else is here, and for [history](/ship/realtime/history) when a client that reconnects must not miss what it was away for.

It is not the right tool for work that has no live audience. If nothing is watching, publish nothing.

## The four ideas

- **Channel** — a named stream of messages, such as `chat:room-1`. Its name is 3 to 128 characters of `a-z`, `A-Z`, `0-9`, `.`, `_`, `-` and `:`, and the first colon is required. You never create a channel: publishing to a name or subscribing to it is enough.
- **Namespace** — the segment before the first colon. `chat:room-1` and `chat:room-2` both live in `chat`. It is 1 to 64 characters of `a-z`, `A-Z`, `0-9`, `.`, `_` and `-`, and cannot contain a colon. It carries the policy for every channel under it.
- **Capability** — one channel name paired with a set of operations. There are exactly four: `subscribe` receives messages, `publish` sends one from a connected client, `presence` enters and reads the roster, `history` replays what was sent earlier. A token carries a list of capabilities, and that list is the entire authority of the connection holding it.
- **Grant** — the durable form of a capability, tying a subject to a channel pattern and a set of operations until you revoke it.

> **A namespace you have not registered allows nothing.** Channels in it are silently dropped wherever capabilities are worked out, so subscribing to `chat:room-1` before `chat` exists never succeeds. Register the namespaces your channels use before you send anything through them.

## What a namespace decides

A namespace is the unit of policy. Registering it decides what any channel under it is allowed to do.

| Setting | Values | Default | What it decides |
|---|---|---|---|
| `visibility` | `public`, `authorized` | `authorized` | `public` gives every authenticated identity `subscribe` on `namespace:*` without a grant |
| `presence` | boolean | `false` | Whether a channel keeps a roster |
| `clientEvents` | boolean | `false` | Whether connected clients may publish directly |
| `history` | `none`, `last-message`, `window` | `none` | What is retained and replayable |
| `historyWindowSeconds` | 1 … 2592000 | — | Retention window; required when `history` is `window` |
| `encryptionRequired` | boolean | `false` | Whether payloads must be end-to-end encrypted |
| `identifiedOnly` | boolean | `false` | Whether anonymous callers get any capability at all |
| `subscribeProxyUrl` | HTTPS URL or `null` | `null` | A callback of yours consulted per channel while a token is minted |
| `archive` | `off`, `{ days: N }` | `off` | Durable retention of archived history entries, up to 3650 days |

Namespaces are registered from the platform, not from the public HTTP surface. Over MCP that is [`realtime_namespace_create`](/reference/mcp-tools/realtime_namespace_create) and [`realtime_namespace_update`](/reference/mcp-tools/realtime_namespace_update); over REST it is on the [Realtime API reference](/reference/openapi/realtime).

## How policy narrows a capability

Nothing that reaches a token is ever wider than the namespace allows. The same rule applies whether the capability comes from a grant or from what your backend declares when it mints a token:

- `presence` survives only if the namespace has `presence` enabled.
- `publish` survives only if the namespace has `clientEvents` enabled.
- `history` survives only if the namespace's `history` is not `none`.
- `subscribe` is not gated by these flags. Who may subscribe is decided by the caller side: a grant, or the channel your backend names when it mints.

Operations that do not survive are **dropped, not refused**. A request for `['subscribe', 'presence']` on a namespace without presence yields a capability of `['subscribe']`. If everything asked for is stripped, the mint fails with `422` rather than handing back a token that would connect and then be refused on every action.

Two more settings act before that narrowing. `identifiedOnly` yields nothing at all to an anonymous caller. `subscribeProxyUrl`, if set, is consulted once per concrete channel while the token is minted, and it is fail-closed: a channel your callback does not allow produces no capability.

## Grants

A grant ties three things together, and stands until you revoke it.

| Field | Meaning |
|---|---|
| `subject` | The identity the grant applies to, or `*` for every identity in the product |
| `pattern` | A channel pattern such as `chat:*` |
| `ops` | The operations granted on every channel the pattern matches |

In a pattern, `*` stands for exactly one segment, and the number of segments must match: `chat:*` matches `chat:room-1` but not `chat:room-1:typing`, and `orders:*:*` matches `orders:acme:42`. The namespace segment is always concrete — you cannot write `*:room-1` — and a pattern carries at most eight segments in total.

Where grants apply, and where they do not, is the practical distinction:

| Caller | Needs a grant | How its capabilities are decided |
|---|---|---|
| A platform identity | Yes | Grants are resolved when a token is minted for that identity, together with the `public` visibility rule |
| One of your own end users | No | Your backend states which concrete channels and operations the token should carry, and namespace policy narrows that declaration. No wildcards |

Grants are managed from the platform: over MCP, [`realtime_grant_create`](/reference/mcp-tools/realtime_grant_create), [`realtime_grant_list`](/reference/mcp-tools/realtime_grant_list) and [`realtime_grant_revoke`](/reference/mcp-tools/realtime_grant_revoke).

## What you build against

| Surface | Use it for |
|---|---|
| `@lessly/realtime` — the [server SDK](/ship/realtime/server-sdk) | Your backend: publish, read history, mint tokens for your users |
| `@lessly/realtime-client` — the [browser client](/ship/realtime/browser-client) | The tab: connect, subscribe, take part in presence, reconnect |
| The public HTTP routes | A backend that does not use the SDK, reached at `https://public.lessly.com/{productId}/realtime` with your API key in the `X-Api-Key` header |

Managing namespaces, grants, API keys and webhooks is not part of that public surface — you do those from the platform.

## Next steps

- [Send your first realtime message](/ship/realtime/quickstart): one path from nothing to a message arriving in a browser tab.
- [Authenticate your backend and your users](/ship/realtime/authentication): the product API key you hold and the short-lived token each user gets.
- [Show who is on a channel](/ship/realtime/presence): turn on the roster and read it.
- [Replay what a client missed](/ship/realtime/history): retain messages and recover after a reconnect.
- [Look up a limit or an error](/ship/realtime/limits-and-errors): every quota, status code and close code in one place.
