Skip to content

Commit 2f33c66

Browse files
committed
docs: document the Sessions HTTP API (reference, channels, scopes)
Adds the Sessions API to the formal API reference: create/list/retrieve/update/close (OpenAPI spec + management/sessions pages), a reference page for the .in/.out realtime channel HTTP endpoints, and a session-scopes section in the authentication docs. Cross-linked with the conceptual ai-chat/sessions page. Documents the shipped HTTP contract for non-SDK and server-to-server callers.
1 parent 034058b commit 2f33c66

10 files changed

Lines changed: 780 additions & 6 deletions

File tree

docs/ai-chat/sessions.mdx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ The two channels mirror the producer/consumer pair in `streams.define` (out) and
197197

198198
## `session.out` — task → clients
199199

200-
The output channel. The task writes; external clients (browser, server action, another task) read via SSE.
200+
The output channel. The task writes; external clients (browser, server action, another task) read via SSE. The underlying HTTP endpoints are documented in [Session channels](/management/sessions/channels) for non-SDK callers.
201201

202202
### `out.append(value, options?)`
203203

@@ -246,7 +246,7 @@ Append an S2 `trim` command. Records with `seq_num < earliestSeqNum` are eventua
246246

247247
## `session.in` — clients → task
248248

249-
The input channel. External clients call `send`; the task consumes via `on` / `once` / `peek` / `wait` / `waitWithIdleTimeout`.
249+
The input channel. External clients call `send`; the task consumes via `on` / `once` / `peek` / `wait` / `waitWithIdleTimeout`. The underlying HTTP endpoints are documented in [Session channels](/management/sessions/channels) for non-SDK callers.
250250

251251
### `in.send(value, requestOptions?)`
252252

@@ -319,8 +319,12 @@ Tokens authorize **both** URL forms: `/sessions/{externalId}/...` and `/sessions
319319

320320
For the `chat.agent` transport, `auth.createPublicToken` is wrapped by `accessToken` in `useTriggerChatTransport`; for direct session access from your server, mint a token per request just like any other realtime resource.
321321

322+
See [Session scopes](/management/authentication#session-scopes) for exactly what `read:sessions` and `write:sessions` grant, and why updating, closing, and appending to `.out` require a secret key.
323+
322324
## See also
323325

326+
- [Sessions HTTP API](/management/sessions/create) — The REST endpoints for creating, listing, retrieving, updating, and closing sessions, plus the [channel endpoints](/management/sessions/channels) for non-SDK callers.
327+
- [Session scopes](/management/authentication#session-scopes) — The public-token scopes that authorize session and channel access.
324328
- [How it works](/ai-chat/how-it-works) — How `chat.agent` builds on Sessions.
325329
- [Backend](/ai-chat/backend)`chat.agent` / `chat.createSession` / raw `task()` with chat primitives.
326330
- [Client Protocol](/ai-chat/client-protocol) — The wire-level view of `.in/append` and `.out` SSE.

docs/docs.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,17 @@
437437
"management/waitpoints/complete-callback"
438438
]
439439
},
440+
{
441+
"group": "Sessions API",
442+
"pages": [
443+
"management/sessions/create",
444+
"management/sessions/list",
445+
"management/sessions/retrieve",
446+
"management/sessions/update",
447+
"management/sessions/close",
448+
"management/sessions/channels"
449+
]
450+
},
440451
{
441452
"group": "Query API",
442453
"pages": [

docs/management/authentication.mdx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,36 @@ Unlike `TriggerClient` instances (which stay isolated unless you opt in), `auth.
189189
concurrency. If you need concurrent multi-target calls there, use
190190
[`new TriggerClient({...})`](/management/multiple-clients) instances instead.
191191
</Note>
192+
193+
## Session scopes
194+
195+
[Sessions](/ai-chat/sessions) are addressed by a session-scoped public access token — a short-lived JWT you mint in your backend and pass to frontend or server-side clients. The token carries one or both of two scopes, each pinned to a session by its friendly ID (`session_…`) or your `externalId`:
196+
197+
| Scope | Grants |
198+
| --- | --- |
199+
| `read:sessions:{id}` | Retrieve and list the session, and subscribe to and drain both its `.in` and `.out` [channels](/management/sessions/channels). |
200+
| `write:sessions:{id}` | Append to the session's `.in` channel, and create runs on the session (including the create call itself). |
201+
202+
Two boundaries follow from the table, and both are enforced server-side:
203+
204+
- **`write:sessions` does not grant `.out` append.** The `.out` channel is the task's to write. Appending to `.out` requires a **secret key**; a public token gets `403`.
205+
- **Updating or closing a session requires a secret key.** A session public token cannot call `PATCH /api/v1/sessions/{session}` or `POST /api/v1/sessions/{session}/close` — those are admin operations.
206+
207+
Mint a token with `auth.createPublicToken` in your backend:
208+
209+
```ts Your backend
210+
import { auth } from "@trigger.dev/sdk";
211+
212+
const publicToken = await auth.createPublicToken({
213+
scopes: {
214+
read: { sessions: "session_123" },
215+
write: { sessions: "session_123" },
216+
},
217+
});
218+
```
219+
220+
`sessions` accepts a single ID or an array. The default token TTL is 1 hour. One token authorizes **both** URL forms — pass either your `externalId` or the `session_…` ID in the path.
221+
222+
The `publicAccessToken` returned by [`sessions.start()`](/management/sessions/create) already carries both scopes for the session it created, so you usually don't mint one by hand for the create flow.
223+
224+
For the full channel HTTP surface these scopes authorize, see [Session channels](/management/sessions/channels). For the SDK side, see [Sessions](/ai-chat/sessions). For general public-token usage (expiration formats, trigger tokens, scoping to runs and tasks), see [Realtime authentication](/realtime/auth).
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
---
2+
title: "Session channels"
3+
sidebarTitle: "Channels"
4+
description: "The raw HTTP endpoints behind a session's .in and .out streams: append records, read them over SSE, and drain them non-streaming."
5+
---
6+
7+
Every session has two durable streams: `.in` carries records from your clients to the task, `.out` carries records from the task back to your clients. The [`sessions` SDK](/ai-chat/sessions) wraps these as `session.in.*` and `session.out.*`. This page documents the underlying HTTP endpoints for callers that aren't using the TypeScript SDK.
8+
9+
All channel endpoints live under `/realtime/v1/sessions/{session}/{io}`, where:
10+
11+
- `{session}` is the session's friendly ID (`session_…`) or your `externalId`. One token authorizes both forms.
12+
- `{io}` is either `in` or `out`.
13+
14+
Authorize requests with a secret key or a [session public token](/management/authentication#session-scopes). The token's scopes decide what you can do — see [Authorization](#authorization) below.
15+
16+
## Append a record
17+
18+
Append a single record to a channel.
19+
20+
```bash Append to .in
21+
curl -X POST "https://api.trigger.dev/realtime/v1/sessions/{session}/in/append" \
22+
-H "Authorization: Bearer $TRIGGER_TOKEN" \
23+
-H "Content-Type: application/json" \
24+
-H "X-Part-Id: 0f8c2b1e-..." \
25+
--data '{"type":"user-message","text":"hello"}'
26+
```
27+
28+
The body is the raw record — any text up to 1MiB (records over the per-record cap return `413`). The response is `{ "ok": true }`.
29+
30+
Set the `X-Part-Id` header to a unique value per record to make the append idempotent: replaying the same `X-Part-Id` does not duplicate the record. Appending to a closed or expired session returns `400`.
31+
32+
<Warning>
33+
Appending to `.out` requires a **secret key**. A session public token (even one with
34+
`write:sessions`) can only append to `.in` — appending to `.out` with a public token returns
35+
`403`. The `.out` stream is the task's to write.
36+
</Warning>
37+
38+
## Read a channel over SSE
39+
40+
Subscribe to a channel as a Server-Sent Events stream. New records are delivered as they arrive.
41+
42+
```bash Read .out
43+
curl -N "https://api.trigger.dev/realtime/v1/sessions/{session}/out" \
44+
-H "Authorization: Bearer $TRIGGER_TOKEN" \
45+
-H "Last-Event-ID: 42" \
46+
-H "Timeout-Seconds: 60"
47+
```
48+
49+
| Header | Direction | Description |
50+
| --- | --- | --- |
51+
| `Last-Event-ID` | request | Resume after this sequence number. Set it to the last `id:` you received to pick up exactly where you left off after a disconnect. |
52+
| `Timeout-Seconds` | request | How long the server holds the stream open with no new records before closing, `1``600`. |
53+
54+
Each SSE event carries:
55+
56+
- `id:` — the record's sequence number. Use the most recent one as `Last-Event-ID` to resume.
57+
- `data:` — a JSON record `{ "data": <record>, "id": <id> }`. For `.out` on a `chat.agent` session, `data` is a UI message chunk (text, reasoning, tool call, or a custom data part).
58+
59+
```text
60+
id: 42
61+
data: {"data":{"type":"text","text":"echo: hello"},"id":42}
62+
```
63+
64+
### Control records
65+
66+
Some `.out` events are **control records** rather than data. A control record has an empty body and carries a `trigger-control` header naming its subtype:
67+
68+
| Subtype | Meaning |
69+
| --- | --- |
70+
| `turn-complete` | The current turn finished. Carries sibling headers `public-access-token` (a refreshed session token), `session-in-event-id`, and `last-event-id`. |
71+
| `upgrade-required` | The session needs to hand off to a run on a newer deployed version. |
72+
73+
Route control records by their subtype instead of treating them as message content. The TypeScript SDK does this for you — `session.out.read` filters control records out of the chunk stream and surfaces them through `onControl`.
74+
75+
## Drain records non-streaming
76+
77+
Fetch a batch of records without holding an SSE connection open. Useful for polling or for reading a tail at startup.
78+
79+
```bash Drain .out
80+
curl "https://api.trigger.dev/realtime/v1/sessions/{session}/out/records?afterEventId=42" \
81+
-H "Authorization: Bearer $TRIGGER_TOKEN"
82+
```
83+
84+
Pass `afterEventId` to return only records after that sequence number; omit it to read from the start of the retained window. The response is:
85+
86+
```json
87+
{
88+
"records": [
89+
{ "data": { "type": "text", "text": "echo: hello" }, "id": 43, "seqNum": 43 }
90+
]
91+
}
92+
```
93+
94+
Each record carries `data`, `id`, `seqNum`, and an optional `headers` array (present on control records). Page forward by passing the highest `seqNum` you received as the next `afterEventId`.
95+
96+
## Authorization
97+
98+
The action you can take depends on your token and the channel:
99+
100+
| Action | Endpoint | Required authorization |
101+
| --- | --- | --- |
102+
| Subscribe (SSE) | `GET .../{io}` | `read:sessions:{id}` — works on both `.in` and `.out` |
103+
| Drain records | `GET .../{io}/records` | `read:sessions:{id}` — works on both `.in` and `.out` |
104+
| Append to `.in` | `POST .../in/append` | `write:sessions:{id}` |
105+
| Append to `.out` | `POST .../out/append` | Secret key only |
106+
107+
Reads work in both directions for a `read:sessions` token. Writes split by direction: a `write:sessions` token can append to `.in`, but `.out` is reserved for the task and requires a secret key. See [session scopes](/management/authentication#session-scopes) for how to mint a token.
108+
109+
## Using the SDK instead
110+
111+
If you're writing TypeScript, the [`sessions` SDK](/ai-chat/sessions) is the ergonomic path. `sessions.open(idOrExternalId)` returns a `SessionHandle` whose `session.in` and `session.out` channels call these endpoints for you, with auto-retry, `Last-Event-ID` resume, and control-record routing built in:
112+
113+
```ts Your backend
114+
import { sessions } from "@trigger.dev/sdk";
115+
116+
const session = sessions.open(chatId);
117+
118+
// append to .in
119+
await session.in.send({ type: "user-message", text: "hello" });
120+
121+
// read .out over SSE
122+
const stream = await session.out.read({ signal: AbortSignal.timeout(30_000) });
123+
for await (const chunk of stream) {
124+
console.log(chunk);
125+
}
126+
```
127+
128+
See [`session.in`](/ai-chat/sessions#session-in-—-clients-→-task) and [`session.out`](/ai-chat/sessions#session-out-—-task-→-clients) for the full handle API.

docs/management/sessions/close.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
title: "Close session"
3+
openapi: "v3-openapi POST /api/v1/sessions/{session}/close"
4+
---
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
title: "Create session"
3+
openapi: "v3-openapi POST /api/v1/sessions"
4+
---

docs/management/sessions/list.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
title: "List sessions"
3+
openapi: "v3-openapi GET /api/v1/sessions"
4+
---
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
title: "Retrieve session"
3+
openapi: "v3-openapi GET /api/v1/sessions/{session}"
4+
---
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
title: "Update session"
3+
openapi: "v3-openapi PATCH /api/v1/sessions/{session}"
4+
---

0 commit comments

Comments
 (0)