.in carries records from your clients to the task, .out carries records from the task back to your clients. The sessions SDK 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 yourexternalId. One token authorizes both forms.{io}is eitherinorout.
Append a record
Append a single record to a channel.Append to .in
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.
Read a channel over SSE
Subscribe to a channel as a Server-Sent Events stream. New records are delivered as they arrive.Read .out
| 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. |
id:— the record’s sequence number. Use the most recent one asLast-Event-IDto resume.data:— a JSON record{ "data": <record>, "id": <id> }. For.outon achat.agentsession,datais a UI message chunk (text, reasoning, tool call, or a custom data part).
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. |
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.Drain .out
afterEventId to return only records after that sequence number; omit it to read from the start of the retained window. The response is:
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 |
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 for how to mint a token.
Using the SDK instead
If you’re writing TypeScript, thesessions SDK 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:
Your backend
session.in and session.out for the full handle API.
