diff --git a/docs/ai-chat/sessions.mdx b/docs/ai-chat/sessions.mdx
index d5ec3abc4c..f003b06f7e 100644
--- a/docs/ai-chat/sessions.mdx
+++ b/docs/ai-chat/sessions.mdx
@@ -138,7 +138,7 @@ console.log(session.currentRunId, session.tags, session.closedAt);
### `sessions.update(idOrExternalId, body, requestOptions?)`
-Mutate `tags`, `metadata`, or `externalId` on an existing Session. Pass `externalId: null` to explicitly clear it.
+Mutate `tags` or `metadata` on an existing Session. `externalId` is read-only after create: it cannot be changed or cleared (it keys the session's durable streams and token scope), so sending a different value returns `422`.
### `sessions.close(idOrExternalId, body?, requestOptions?)`
@@ -197,7 +197,7 @@ The two channels mirror the producer/consumer pair in `streams.define` (out) and
## `session.out` — task → clients
-The output channel. The task writes; external clients (browser, server action, another task) read via SSE.
+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.
### `out.append(value, options?)`
@@ -246,7 +246,7 @@ Append an S2 `trim` command. Records with `seq_num < earliestSeqNum` are eventua
## `session.in` — clients → task
-The input channel. External clients call `send`; the task consumes via `on` / `once` / `peek` / `wait` / `waitWithIdleTimeout`.
+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.
### `in.send(value, requestOptions?)`
@@ -319,8 +319,12 @@ Tokens authorize **both** URL forms: `/sessions/{externalId}/...` and `/sessions
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.
+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.
+
## See also
+- [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.
+- [Session scopes](/management/authentication#session-scopes) — The public-token scopes that authorize session and channel access.
- [How it works](/ai-chat/how-it-works) — How `chat.agent` builds on Sessions.
- [Backend](/ai-chat/backend) — `chat.agent` / `chat.createSession` / raw `task()` with chat primitives.
- [Client Protocol](/ai-chat/client-protocol) — The wire-level view of `.in/append` and `.out` SSE.
diff --git a/docs/docs.json b/docs/docs.json
index e2cb83db04..367b99a914 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -437,6 +437,17 @@
"management/waitpoints/complete-callback"
]
},
+ {
+ "group": "Sessions API",
+ "pages": [
+ "management/sessions/create",
+ "management/sessions/list",
+ "management/sessions/retrieve",
+ "management/sessions/update",
+ "management/sessions/close",
+ "management/sessions/channels"
+ ]
+ },
{
"group": "Query API",
"pages": [
diff --git a/docs/management/authentication.mdx b/docs/management/authentication.mdx
index ce85f23cb9..b467fe531a 100644
--- a/docs/management/authentication.mdx
+++ b/docs/management/authentication.mdx
@@ -189,3 +189,36 @@ Unlike `TriggerClient` instances (which stay isolated unless you opt in), `auth.
concurrency. If you need concurrent multi-target calls there, use
[`new TriggerClient({...})`](/management/multiple-clients) instances instead.
+
+## Session scopes
+
+[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`:
+
+| Scope | Grants |
+| --- | --- |
+| `read:sessions:{id}` | Retrieve the session, list its runs, and subscribe to and drain both its `.in` and `.out` [channels](/management/sessions/channels). |
+| `write:sessions:{id}` | Append to the session's `.in` channel, and create runs on the session (including the create call itself). |
+
+Two boundaries follow from the table, and both are enforced server-side:
+
+- **`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`.
+- **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.
+
+Mint a token with `auth.createPublicToken` in your backend:
+
+```ts Your backend
+import { auth } from "@trigger.dev/sdk";
+
+const publicToken = await auth.createPublicToken({
+ scopes: {
+ read: { sessions: "session_123" },
+ write: { sessions: "session_123" },
+ },
+});
+```
+
+`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.
+
+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.
+
+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).
diff --git a/docs/management/sessions/channels.mdx b/docs/management/sessions/channels.mdx
new file mode 100644
index 0000000000..f8508eb8ce
--- /dev/null
+++ b/docs/management/sessions/channels.mdx
@@ -0,0 +1,128 @@
+---
+title: "Session channels"
+sidebarTitle: "Channels"
+description: "The raw HTTP endpoints behind a session's .in and .out streams: append records, read them over SSE, and drain them non-streaming."
+---
+
+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.
+
+All channel endpoints live under `/realtime/v1/sessions/{session}/{io}`, where:
+
+- `{session}` is the session's friendly ID (`session_…`) or your `externalId`. One token authorizes both forms.
+- `{io}` is either `in` or `out`.
+
+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.
+
+## Append a record
+
+Append a single record to a channel.
+
+```bash Append to .in
+curl -X POST "https://api.trigger.dev/realtime/v1/sessions/{session}/in/append" \
+ -H "Authorization: Bearer $TRIGGER_TOKEN" \
+ -H "Content-Type: application/json" \
+ -H "X-Part-Id: 0f8c2b1e-..." \
+ --data '{"type":"user-message","text":"hello"}'
+```
+
+The body is the raw record — any text up to 1MiB (records over the per-record cap return `413`). The response is `{ "ok": true }`.
+
+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`.
+
+
+ Appending to `.out` requires a **secret key**. A session public token (even one with
+ `write:sessions`) can only append to `.in` — appending to `.out` with a public token returns
+ `403`. The `.out` stream is the task's to write.
+
+
+## Read a channel over SSE
+
+Subscribe to a channel as a Server-Sent Events stream. New records are delivered as they arrive.
+
+```bash Read .out
+curl -N "https://api.trigger.dev/realtime/v1/sessions/{session}/out" \
+ -H "Authorization: Bearer $TRIGGER_TOKEN" \
+ -H "Last-Event-ID: 42" \
+ -H "Timeout-Seconds: 60"
+```
+
+| Header | Direction | Description |
+| --- | --- | --- |
+| `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. |
+| `Timeout-Seconds` | request | How long the server holds the stream open with no new records before closing, `1`–`600`. |
+
+Each SSE event carries:
+
+- `id:` — the record's sequence number. Use the most recent one as `Last-Event-ID` to resume.
+- `data:` — a JSON record `{ "data": , "id": }`. For `.out` on a `chat.agent` session, `data` is a UI message chunk (text, reasoning, tool call, or a custom data part).
+
+```text
+id: 42
+data: {"data":{"type":"text","text":"echo: hello"},"id":42}
+```
+
+### Control records
+
+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:
+
+| Subtype | Meaning |
+| --- | --- |
+| `turn-complete` | The current turn finished. Carries sibling headers `public-access-token` (a refreshed session token), `session-in-event-id`, and `last-event-id`. |
+| `upgrade-required` | The session needs to hand off to a run on a newer deployed version. |
+
+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`.
+
+## Drain records non-streaming
+
+Fetch a batch of records without holding an SSE connection open. Useful for polling or for reading a tail at startup.
+
+```bash Drain .out
+curl "https://api.trigger.dev/realtime/v1/sessions/{session}/out/records?afterEventId=42" \
+ -H "Authorization: Bearer $TRIGGER_TOKEN"
+```
+
+Pass `afterEventId` to return only records after that sequence number; omit it to read from the start of the retained window. The response is:
+
+```json
+{
+ "records": [
+ { "data": { "type": "text", "text": "echo: hello" }, "id": 43, "seqNum": 43 }
+ ]
+}
+```
+
+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`.
+
+## Authorization
+
+The action you can take depends on your token and the channel:
+
+| Action | Endpoint | Required authorization |
+| --- | --- | --- |
+| Subscribe (SSE) | `GET .../{io}` | `read:sessions:{id}` — works on both `.in` and `.out` |
+| Drain records | `GET .../{io}/records` | `read:sessions:{id}` — works on both `.in` and `.out` |
+| Append to `.in` | `POST .../in/append` | `write:sessions:{id}` |
+| Append to `.out` | `POST .../out/append` | Secret key only |
+
+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.
+
+## Using the SDK instead
+
+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:
+
+```ts Your backend
+import { sessions } from "@trigger.dev/sdk";
+
+const session = sessions.open(chatId);
+
+// append to .in
+await session.in.send({ type: "user-message", text: "hello" });
+
+// read .out over SSE
+const stream = await session.out.read({ signal: AbortSignal.timeout(30_000) });
+for await (const chunk of stream) {
+ console.log(chunk);
+}
+```
+
+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.
diff --git a/docs/management/sessions/close.mdx b/docs/management/sessions/close.mdx
new file mode 100644
index 0000000000..a89d229c5d
--- /dev/null
+++ b/docs/management/sessions/close.mdx
@@ -0,0 +1,4 @@
+---
+title: "Close session"
+openapi: "v3-openapi POST /api/v1/sessions/{session}/close"
+---
diff --git a/docs/management/sessions/create.mdx b/docs/management/sessions/create.mdx
new file mode 100644
index 0000000000..f890c13828
--- /dev/null
+++ b/docs/management/sessions/create.mdx
@@ -0,0 +1,4 @@
+---
+title: "Create session"
+openapi: "v3-openapi POST /api/v1/sessions"
+---
diff --git a/docs/management/sessions/list.mdx b/docs/management/sessions/list.mdx
new file mode 100644
index 0000000000..c3406c5f8b
--- /dev/null
+++ b/docs/management/sessions/list.mdx
@@ -0,0 +1,4 @@
+---
+title: "List sessions"
+openapi: "v3-openapi GET /api/v1/sessions"
+---
diff --git a/docs/management/sessions/retrieve.mdx b/docs/management/sessions/retrieve.mdx
new file mode 100644
index 0000000000..23b1ab2c44
--- /dev/null
+++ b/docs/management/sessions/retrieve.mdx
@@ -0,0 +1,4 @@
+---
+title: "Retrieve session"
+openapi: "v3-openapi GET /api/v1/sessions/{session}"
+---
diff --git a/docs/management/sessions/update.mdx b/docs/management/sessions/update.mdx
new file mode 100644
index 0000000000..0f77c0be17
--- /dev/null
+++ b/docs/management/sessions/update.mdx
@@ -0,0 +1,4 @@
+---
+title: "Update session"
+openapi: "v3-openapi PATCH /api/v1/sessions/{session}"
+---
diff --git a/docs/v3-openapi.yaml b/docs/v3-openapi.yaml
index fa052e7c44..56100db3fe 100644
--- a/docs/v3-openapi.yaml
+++ b/docs/v3-openapi.yaml
@@ -3257,6 +3257,264 @@ paths:
console.log(token.output);
}
+ "/api/v1/sessions":
+ post:
+ operationId: create_session_v1
+ summary: Create a session
+ description: >-
+ Create a Session and trigger its first run in one atomic call. A Session is
+ the durable identity for a bi-directional stream of records (the `.in` and
+ `.out` channels) that survives across the runs processing it.
+
+
+ Idempotent on `externalId` within an environment. Calling create again with
+ an `externalId` that already maps to an open session returns the existing
+ session with `isCached: true` and `201` becomes `200`. Reusing an
+ `externalId` whose session is already closed or expired returns `409`.
+
+
+ Authorize with a secret key, or a public token carrying `write:sessions` for
+ the session you are creating.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ "$ref": "#/components/schemas/CreateSessionRequestBody"
+ responses:
+ "201":
+ description: Session created and its first run triggered.
+ content:
+ application/json:
+ schema:
+ "$ref": "#/components/schemas/CreatedSessionResponseBody"
+ "200":
+ description: >-
+ An open session already existed for this `externalId`. The existing
+ session is returned with `isCached: true`.
+ content:
+ application/json:
+ schema:
+ "$ref": "#/components/schemas/CreatedSessionResponseBody"
+ "401":
+ description: Unauthorized
+ "409":
+ description: >-
+ An `externalId` was reused, but its session is already closed or expired.
+ Closed and expired sessions cannot be reopened.
+ content:
+ application/json:
+ schema:
+ "$ref": "#/components/schemas/ErrorResponse"
+ "422":
+ description: >-
+ Validation failed — for example the request body exceeds 32KB, or
+ `externalId` starts with the reserved `session_` prefix.
+ content:
+ application/json:
+ schema:
+ "$ref": "#/components/schemas/ErrorWithDetailsResponse"
+ tags:
+ - sessions
+ security:
+ - secretKey: []
+ - publicAccessToken: []
+ x-codeSamples:
+ - lang: typescript
+ label: Start a session
+ source: |-
+ import { sessions } from "@trigger.dev/sdk";
+
+ const { id, runId, publicAccessToken, isCached } = await sessions.start({
+ type: "chat.agent",
+ externalId: chatId,
+ taskIdentifier: "my-chat",
+ triggerConfig: {
+ basePayload: { chatId },
+ tags: [`chat:${chatId}`],
+ },
+ });
+
+ console.log(id); // e.g. "session_abc123"
+ console.log(runId); // the first run, e.g. "run_def456"
+ console.log(isCached); // false on a brand-new session
+
+ get:
+ operationId: list_sessions_v1
+ summary: List sessions
+ description: >-
+ List sessions in the current environment, newest first. Filter by type, tags,
+ task identifier, external id, status, and creation window. Use cursor-based
+ pagination with `page[after]` and `page[before]` to navigate pages.
+
+
+ List rows omit `triggerConfig`; retrieve a single session to read it.
+ parameters:
+ - $ref: "#/components/parameters/sessionsCursorPagination"
+ - $ref: "#/components/parameters/sessionsFilter"
+ responses:
+ "200":
+ description: Successful request
+ content:
+ application/json:
+ schema:
+ "$ref": "#/components/schemas/ListSessionsResult"
+ "400":
+ description: Invalid query parameters
+ content:
+ application/json:
+ schema:
+ "$ref": "#/components/schemas/ErrorWithDetailsResponse"
+ "401":
+ description: Unauthorized request
+ tags:
+ - sessions
+ security:
+ - secretKey: []
+ - publicAccessToken: []
+ x-codeSamples:
+ - lang: typescript
+ label: List sessions
+ source: |-
+ import { sessions } from "@trigger.dev/sdk";
+
+ for await (const session of sessions.list({
+ type: "chat.agent",
+ status: "ACTIVE",
+ limit: 50,
+ })) {
+ console.log(session.id, session.externalId, session.createdAt);
+ }
+
+ "/api/v1/sessions/{session}":
+ parameters:
+ - $ref: "#/components/parameters/session"
+ get:
+ operationId: retrieve_session_v1
+ summary: Retrieve a session
+ description: >-
+ Retrieve a single session by its friendly id (`session_…`) or your
+ `externalId`. The response includes `triggerConfig` and the friendly
+ `currentRunId` of the live run, if any.
+ responses:
+ "200":
+ description: Successful request
+ content:
+ application/json:
+ schema:
+ "$ref": "#/components/schemas/SessionObject"
+ "401":
+ description: Unauthorized request
+ "404":
+ description: Session not found
+ tags:
+ - sessions
+ security:
+ - secretKey: []
+ - publicAccessToken: []
+ x-codeSamples:
+ - lang: typescript
+ label: Retrieve a session
+ source: |-
+ import { sessions } from "@trigger.dev/sdk";
+
+ const session = await sessions.retrieve(chatId);
+
+ console.log(session.currentRunId, session.tags, session.closedAt);
+
+ patch:
+ operationId: update_session_v1
+ summary: Update a session
+ description: >-
+ Update a session's `tags` or `metadata`. Pass `metadata: null` to clear it.
+
+
+ Requires a secret key — a session public token cannot update a session.
+ `externalId` is read-only after create: it cannot be changed or cleared.
+ Sending a value different from the current one (including `null` when one is
+ set) returns `422`; sending the same value is accepted as a no-op.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ "$ref": "#/components/schemas/UpdateSessionRequestBody"
+ responses:
+ "200":
+ description: Session updated successfully
+ content:
+ application/json:
+ schema:
+ "$ref": "#/components/schemas/SessionObject"
+ "401":
+ description: Unauthorized request
+ "404":
+ description: Session not found
+ "422":
+ description: >-
+ Validation failed — for example an attempt to change `externalId` to a
+ different value, or a body exceeding 32KB.
+ content:
+ application/json:
+ schema:
+ "$ref": "#/components/schemas/ErrorWithDetailsResponse"
+ tags:
+ - sessions
+ security:
+ - secretKey: []
+ x-codeSamples:
+ - lang: typescript
+ label: Update a session
+ source: |-
+ import { sessions } from "@trigger.dev/sdk";
+
+ const session = await sessions.update(chatId, {
+ tags: ["priority"],
+ metadata: { lastSeenBy: "support" },
+ });
+
+ "/api/v1/sessions/{session}/close":
+ parameters:
+ - $ref: "#/components/parameters/session"
+ post:
+ operationId: close_session_v1
+ summary: Close a session
+ description: >-
+ Close a session. Closing is terminal and idempotent — closing an
+ already-closed session returns the existing row unchanged. A closed session
+ cannot be reopened, and reusing its `externalId` on create returns `409`.
+
+
+ Requires a secret key — a session public token cannot close a session.
+ requestBody:
+ required: false
+ content:
+ application/json:
+ schema:
+ "$ref": "#/components/schemas/CloseSessionRequestBody"
+ responses:
+ "200":
+ description: Session closed successfully. Returns the session row.
+ content:
+ application/json:
+ schema:
+ "$ref": "#/components/schemas/SessionObject"
+ "401":
+ description: Unauthorized request
+ "404":
+ description: Session not found
+ tags:
+ - sessions
+ security:
+ - secretKey: []
+ x-codeSamples:
+ - lang: typescript
+ label: Close a session
+ source: |-
+ import { sessions } from "@trigger.dev/sdk";
+
+ await sessions.close(chatId, { reason: "user signed out" });
+
components:
parameters:
taskIdentifier:
@@ -3319,6 +3577,30 @@ components:
before:
type: string
description: The ID of the run to start the page before. This will set the direction of the pagination to backward.
+ sessionsCursorPagination:
+ in: query
+ name: page
+ style: deepObject
+ explode: true
+ description: |
+ Paginate the results. Specify the number of sessions per page, and the ID of the session to start the page after or before.
+
+ For object fields like `page`, use the "form" encoding style. For example, to get the next page, use `page[after]=session_1234`.
+ schema:
+ type: object
+ properties:
+ size:
+ type: integer
+ maximum: 100
+ minimum: 1
+ default: 20
+ description: Number of sessions per page. Maximum is 100.
+ after:
+ type: string
+ description: The ID of the session to start the page after. Sets the pagination direction to forward.
+ before:
+ type: string
+ description: The ID of the session to start the page before. Sets the pagination direction to backward.
runId:
in: path
name: runId
@@ -3361,6 +3643,29 @@ components:
type: string
description: The name of the environment variable.
example: SLACK_API_KEY
+ session:
+ in: path
+ name: session
+ required: true
+ schema:
+ type: string
+ description: >-
+ The session's friendly ID (`session_…`) or your `externalId`. The server
+ disambiguates by the `session_` prefix.
+ example: session_abc123
+ sessionsFilter:
+ in: query
+ name: filter
+ style: deepObject
+ explode: true
+ description: |
+ Use this parameter to filter the sessions. You can filter by type, tags, task identifier, external id, status, and created at.
+
+ For array fields, you can provide multiple values to filter by using a comma-separated list. For example, to get ACTIVE and CLOSED sessions, you can use `filter[status]=ACTIVE,CLOSED`.
+
+ For object fields, you should use the "form" encoding style. For example, to filter by the period, you can use `filter[createdAt][period]=1d`.
+ schema:
+ $ref: "#/components/schemas/SessionsFilter"
securitySchemes:
secretKey:
type: http
@@ -3396,11 +3701,13 @@ components:
type: http
scheme: bearer
description: |
- A short-lived JWT scoped to a specific waitpoint token. Returned as `publicAccessToken`
- when you call `wait.createToken()` or `POST /api/v1/waitpoints/tokens`.
+ A short-lived JWT scoped to specific resources, returned as `publicAccessToken` from APIs
+ such as `wait.createToken()` / `POST /api/v1/waitpoints/tokens` and
+ `sessions.start()` / `POST /api/v1/sessions`, or minted directly with `auth.createPublicToken()`.
- This token is safe to embed in frontend clients — it can only complete the specific
- waitpoint it was issued for and cannot be used for any other API operations.
+ This token is safe to embed in frontend clients — it can only act on the resources its
+ scopes grant (e.g. `read:sessions:{id}`, `write:sessions:{id}`) and cannot be used for any
+ other API operations. For session tokens see the [session scopes](/management/authentication#session-scopes).
schemas:
RunTag:
@@ -3895,6 +4202,303 @@ components:
example: 491
description: The duration of compute (so far) in milliseconds. This does not include waits.
+ SessionTriggerConfig:
+ type: object
+ description: >-
+ Trigger options applied to every run a session schedules. `basePayload` is the
+ wire payload merged into each run; the remaining fields map onto the standard
+ trigger options.
+ required:
+ - basePayload
+ properties:
+ basePayload:
+ type: object
+ additionalProperties: true
+ description: >-
+ Base payload passed to every run this session triggers. For `chat.agent`
+ this carries `{ chatId, ...clientData }`.
+ machine:
+ type: string
+ description: Machine preset for each run, e.g. `small-1x`.
+ example: small-1x
+ queue:
+ type: string
+ maxLength: 128
+ description: Queue to schedule runs on.
+ tags:
+ type: array
+ maxItems: 5
+ items:
+ type: string
+ maxLength: 128
+ description: Tags applied to every run this session triggers.
+ maxAttempts:
+ type: integer
+ minimum: 1
+ maximum: 10
+ description: Maximum retry attempts per run.
+ maxDuration:
+ type: integer
+ minimum: 1
+ description: Per-run wall-clock cap in seconds.
+ lockToVersion:
+ type: string
+ description: Pin every run to a specific worker version.
+ example: "20240523.1"
+ region:
+ type: string
+ description: Region to schedule runs in.
+ idleTimeoutInSeconds:
+ type: integer
+ minimum: 1
+ maximum: 3600
+ description: Idle timeout surfaced to `chat.agent` via the wire payload.
+ CreateSessionRequestBody:
+ type: object
+ description: >-
+ Body for `POST /api/v1/sessions`. The whole body must be 32KB or smaller.
+ required:
+ - type
+ - taskIdentifier
+ - triggerConfig
+ properties:
+ type:
+ type: string
+ minLength: 1
+ maxLength: 64
+ description: >-
+ Free-form discriminator for the session, e.g. `chat.agent`. Not validated
+ against an enum.
+ example: chat.agent
+ taskIdentifier:
+ type: string
+ minLength: 1
+ maxLength: 128
+ description: The task this session triggers runs against.
+ example: my-chat
+ triggerConfig:
+ $ref: "#/components/schemas/SessionTriggerConfig"
+ externalId:
+ type: string
+ minLength: 1
+ maxLength: 256
+ description: >-
+ Your stable identity for the session, unique per environment. Cannot start
+ with the reserved `session_` prefix. Reusing an `externalId` makes create
+ idempotent; reusing one whose session is closed or expired returns `409`.
+ example: chat_1234
+ tags:
+ type: array
+ maxItems: 10
+ items:
+ type: string
+ maxLength: 128
+ description: Up to 10 tags on the session row, for dashboard filtering.
+ metadata:
+ type: object
+ additionalProperties: true
+ description: Arbitrary JSON metadata.
+ expiresAt:
+ type: string
+ format: date-time
+ description: Absolute expiry timestamp for retention.
+ SessionObject:
+ type: object
+ description: A session row.
+ required:
+ - id
+ - type
+ - taskIdentifier
+ - tags
+ - createdAt
+ - updatedAt
+ properties:
+ id:
+ type: string
+ description: The session's friendly ID, prefixed with `session_`.
+ example: session_abc123
+ externalId:
+ type: string
+ nullable: true
+ description: Your stable identity for the session, if one was set.
+ example: chat_1234
+ type:
+ type: string
+ description: The session type discriminator.
+ example: chat.agent
+ taskIdentifier:
+ type: string
+ description: The task this session triggers runs against.
+ example: my-chat
+ triggerConfig:
+ $ref: "#/components/schemas/SessionTriggerConfig"
+ currentRunId:
+ type: string
+ nullable: true
+ description: >-
+ Friendly ID of the live run for this session, if any. Prefixed with `run_`.
+ Omitted on list rows.
+ example: run_def456
+ tags:
+ type: array
+ items:
+ type: string
+ description: Tags on the session row.
+ example: ["chat:1234"]
+ metadata:
+ type: object
+ additionalProperties: true
+ nullable: true
+ description: Arbitrary JSON metadata, or `null` if unset.
+ closedAt:
+ type: string
+ format: date-time
+ nullable: true
+ description: When the session was closed, or `null` if open.
+ closedReason:
+ type: string
+ nullable: true
+ description: The optional reason recorded when the session was closed.
+ expiresAt:
+ type: string
+ format: date-time
+ nullable: true
+ description: The session's retention deadline, or `null` if none.
+ createdAt:
+ type: string
+ format: date-time
+ updatedAt:
+ type: string
+ format: date-time
+ CreatedSessionResponseBody:
+ allOf:
+ - $ref: "#/components/schemas/SessionObject"
+ - type: object
+ required:
+ - runId
+ - publicAccessToken
+ - isCached
+ properties:
+ runId:
+ type: string
+ description: Friendly ID of the first run triggered alongside the session.
+ example: run_def456
+ publicAccessToken:
+ type: string
+ description: >-
+ Session-scoped public access token carrying `read:sessions:{key}` and
+ `write:sessions:{key}`. Default TTL is 1 hour. Safe to pass to frontend
+ clients.
+ isCached:
+ type: boolean
+ description: >-
+ `true` if an open session already existed for this `externalId`
+ (idempotent upsert), `false` if newly created.
+ UpdateSessionRequestBody:
+ type: object
+ description: >-
+ Body for `PATCH /api/v1/sessions/{session}`. The whole body must be 32KB or
+ smaller. Every field is optional; omitted fields are left unchanged.
+ properties:
+ tags:
+ type: array
+ maxItems: 10
+ items:
+ type: string
+ maxLength: 128
+ description: Replaces the tags on the session row.
+ metadata:
+ type: object
+ additionalProperties: true
+ nullable: true
+ description: Replaces the metadata. Pass `null` to clear it.
+ externalId:
+ type: string
+ nullable: true
+ minLength: 1
+ maxLength: 256
+ description: >-
+ Read-only after create: cannot be changed or cleared. Sending a value
+ different from the current one (including `null` when one is set) returns
+ `422`; sending the same value is idempotent.
+ CloseSessionRequestBody:
+ type: object
+ description: Body for `POST /api/v1/sessions/{session}/close`. Up to 1KB.
+ properties:
+ reason:
+ type: string
+ maxLength: 256
+ description: Optional reason recorded on the session row.
+ example: user signed out
+ SessionsFilter:
+ type: object
+ properties:
+ type:
+ type: array
+ items:
+ type: string
+ description: The session type(s) to filter by.
+ example: ["chat.agent"]
+ tags:
+ type: array
+ items:
+ type: string
+ description: The tags to filter by.
+ taskIdentifier:
+ type: array
+ items:
+ type: string
+ description: The task identifier(s) to filter by.
+ externalId:
+ type: string
+ description: Exact match on your `externalId`.
+ status:
+ type: array
+ items:
+ type: string
+ enum:
+ - ACTIVE
+ - CLOSED
+ - EXPIRED
+ description: The lifecycle status(es) to filter by.
+ createdAt:
+ type: object
+ properties:
+ from:
+ type: string
+ format: date-time
+ description: The start date to filter the sessions by.
+ to:
+ type: string
+ format: date-time
+ description: The end date to filter the sessions by.
+ period:
+ type: string
+ description: The period to filter the sessions by.
+ example: 1d
+ ListSessionsResult:
+ type: object
+ properties:
+ data:
+ type: array
+ items:
+ "$ref": "#/components/schemas/SessionObject"
+ pagination:
+ type: object
+ properties:
+ next:
+ type: string
+ description: >-
+ The session ID to start the next page after. Pass it as the
+ `page[after]` parameter on the next request.
+ example: session_abc123
+ previous:
+ type: string
+ description: >-
+ The session ID to start the previous page before. Pass it as the
+ `page[before]` parameter on the next request.
+ example: session_xyz789
+
InvalidEnvVarsRequestResponse:
type: object
properties: