> For the complete documentation index, see [llms.txt](https://docs.fastrouter.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.fastrouter.ai/api-reference/sessions.md).

# Sessions

## Create Live Session (WebSocket)

> Opens a WebSocket to a GPT-Live session for real-time voice (and text-context) conversations. FastRouter proxies the session to OpenAI, validates the model, and injects \`cost\` into the usage events so you can see what each session is charged.\
> \
> \*\*Connection URL\*\*\
> \`\`\`\
> wss\://api.fastrouter.ai/v1/live/sessions?model=openai/gpt-live-1\
> \`\`\`\
> \
> \*\*Authentication\*\* (checked before the upgrade): \`Authorization: Bearer \<key>\` or \`x-api-key: \<key>\` header, or \`?api\_key=\<key>\` in the URL for clients that cannot set WebSocket headers (browsers).\
> \
> \*\*Models.\*\* \`model\` (query, or \`session.model\` in \`session.start\`) is the \*live voice model\* — a FastRouter catalogue id such as \`openai/gpt-live-1\` (the bare \`gpt-live-1\` is also accepted). An unknown model is rejected with \`400 model\_not\_found\`. The \*delegation model\* (below) is a separate OpenAI text model.\
> \
> \### Protocol\
> \
> Messages are JSON events in both directions. \*\*The server sends nothing until you send \`session.start\`\*\* — it must be your first message. Reply: \`session.started\` with the session object, including \`session.id\`.\
> \
> \`\`\`json\
> {"type": "session.start", "event\_id": "start-1", "session": {\
> &#x20; "model": "openai/gpt-live-1",\
> &#x20; "instructions": "You are a concise assistant.",\
> &#x20; "audio": {"format": {"type": "audio/pcm", "rate": 24000}, "output": {"voice": "marin"}},\
> &#x20; "store": false,\
> &#x20; "delegation": {"type": "responses", "responses": {"model": "gpt-4o-mini"}}\
> }}\
> \`\`\`\
> \
> \`session\` fields: \`model\`, \`instructions\`, \`audio\` (\`format.type\`: \`audio/pcm\` at 24000 or 16000, \`audio/pcmu\` or \`audio/pcma\` at 8000; \`output.voice\`), \`input\` (seed text history: \`\[{"type":"message","role":"user","content":\[{"type":"input\_text","text":"..."}]}]\`), \`delegation\`, \`store\`.\
> \
> \*\*Client events\*\* (forwarded to OpenAI as-is; any other \`type\` — including OpenAI \*Realtime\* names such as \`conversation.item.create\` — is rejected with an \`error\` event):\
> \
> \| Event | Payload | Notes |\
> \|---|---|---|\
> \| \`session.start\` | \`{"session": {...}}\` | First message. Fixes \`delegation\` for the session's lifetime. |\
> \| \`session.update\` | \`{"session": {...}}\` | Update instructions/audio. Cannot change \`delegation.type\`. |\
> \| \`session.input\_audio.append\` | \`{"audio": "\<base64>"}\` | Stream microphone audio continuously. There is no commit — GPT-Live decides when to reply. |\
> \| \`session.input\_audio.mute\` / \`.unmute\` | \`{}\` | Pause/resume input while the session keeps running. |\
> \| \`session.instructions.append\` | \`{"content": "..."}\` | Add instructions mid-session. |\
> \| \`session.thinking.append\` | \`{"content": "..."}\` | Add private context the model should consider. |\
> \| \`session.commentary.append\` | \`{"content": "..."}\` | Add context the model may speak about. |\
> \| \`response.item.create\` | OpenAI Responses input item | Requires \`delegation.type: "responses"\`. |\
> \| \`response.create\` | \`{}\` or Responses request fields | Requires \`delegation.type: "responses"\`; runs a turn on the delegation model. |\
> \| \`session.close\` | \`{}\` | End the session; \`session.closed\` follows with final usage. |\
> \
> Every event may carry \`event\_id\`; acknowledgements and errors echo it as \`client\_event\_id\`.\
> \
> \*\*Delegation.\*\* \`delegation.type\` is \`"client"\` (default — the live model handles everything itself) or \`"responses"\` — delegated work (tool calls, \`response.create\` turns) runs on an OpenAI \*Responses API\* model. With \`"responses"\`, \`delegation.responses.model\` is required and is sent to OpenAI verbatim, so use OpenAI's bare id (e.g. \`gpt-4o-mini\`), not the \`openai/\` catalogue form. Optional \`delegation.responses\` fields: \`instructions\`, \`tools\`, \`tool\_choice\`, \`parallel\_tool\_calls\`, \`max\_output\_tokens\`, \`service\_tier\`, \`reasoning\`, \`text\`.\
> \
> \*\*Server events\*\*\
> \
> \| Event | Payload | Notes |\
> \|---|---|---|\
> \| \`session.started\` / \`session.updated\` | \`{"session": {"id": "...", ...}}\` | \`session.id\` is the id used for forking. |\
> \| \`session.output\_audio.delta\` | \`{"delta": "\<base64>"}\` | Spoken reply, in the session's audio format. |\
> \| \`session.input\_transcript.delta\` / \`session.output\_transcript.delta\` | \`{"delta": "...", "start\_ms", "end\_ms"}\` | Transcripts of what was heard / said. |\
> \| \`session.usage.updated\` | \`{"usage": {"seconds": 12.4, "cost": 0.0021}}\` | Periodic snapshot (cumulative, not a delta). \`cost\` is added by FastRouter. |\
> \| \`session.delegation.created\` | \`{"delegation\_id": "..."}\` | A delegated task started. |\
> \| \`response.event\` | \`{"delegation\_id": "...", "event": {...}}\` | Wraps a nested OpenAI Responses event: \`response.created\`, \`response.output\_text.delta\` (\`event.delta\` holds the text), \`response.output\_item.added/done\`, \`response.completed\` (\`event.response.usage\` has tokens and a FastRouter-added \`cost\`). |\
> \| \`session.\*.appended\`, \`session.input\_audio.muted/unmuted\` | \`{"client\_event\_id": "..."}\` | Acknowledgements. |\
> \| \`session.closed\` | \`{"reason": "...", "usage": {"seconds", "cost"}}\` | Final usage. \`reason\` is e.g. \`expired\` when OpenAI's session lifetime is reached, or set by \`session.close\`. |\
> \| \`error\` | \`{"error": {"type", "code", "message", "param", "client\_event\_id"}}\` | Validation and upstream errors. |\
> \
> \*\*Billing.\*\* Voice time is billed per second of session audio at the live model's rate (\`usage.seconds\`); delegated responses are billed per token at the delegation model's rate. Both \`cost\` values are in FastRouter credits and appear on the events above. Session lifetime and idle limits are set by OpenAI — expect \`session.closed\` with \`reason: "expired"\` rather than an indefinite session.\
> \
> \*\*Minimal client\*\*\
> \`\`\`javascript\
> const ws = new WebSocket("wss\://api.fastrouter.ai/v1/live/sessions?model=openai/gpt-live-1\&api\_key=" + KEY);\
> ws.onopen = () => ws.send(JSON.stringify({type: "session.start", session: {\
> &#x20; audio: {format: {type: "audio/pcm", rate: 24000}},\
> &#x20; delegation: {type: "responses", responses: {model: "gpt-4o-mini"}}\
> }}));\
> ws.onmessage = ({data}) => {\
> &#x20; const ev = JSON.parse(data);\
> &#x20; if (ev.type === "session.started") {\
> &#x20;   ws.send(JSON.stringify({type: "response.item.create", item: {type: "message", role: "user", content: \[{type: "input\_text", text: "Say hello in one word."}]}}));\
> &#x20;   ws.send(JSON.stringify({type: "response.create"}));\
> &#x20; }\
> &#x20; if (ev.type === "response.event" && ev.event.type === "response.output\_text.delta") process.stdout.write(ev.event.delta);\
> &#x20; if (ev.type === "response.event" && ev.event.type === "response.completed") ws.send(JSON.stringify({type: "session.close"}));\
> &#x20; if (ev.type === "session.closed") console.log("\ncost:", ev.usage.cost);\
> };\
> \`\`\`\
> A full browser client with microphone streaming is in \`examples/live\_client.html\`.

````json
{"openapi":"3.1.0","info":{"title":"FastRouter API Reference","version":"1.0.0"},"tags":[{"name":"Live Sessions","description":"GPT-Live WebSocket sessions API for real-time voice and text conversations using OpenAI's gpt-live models. Supports starting a new session and forking an existing stored session."}],"servers":[{"url":"https://api.fastrouter.ai","description":"Production API"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"FastRouter API Key. Get yours at https://fastrouter.ai\n\nFormat: `Authorization: Bearer YOUR_API_KEY`"}},"responses":{"BadRequestError":{"description":"Bad Request - The request is malformed. This could be due to missing parameters, invalid formats, or routing errors.\n\n**Note:** the 400 body shape is not uniform. Validation errors return the structured object below, but routing/model errors may instead return a plain string: `{ \"error\": \"<message>\" }`. A few endpoints (e.g. some file operations) may return a plain-text body rather than JSON.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","description":"Structured validation error.","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"type":"string"},"code":{"type":"string"}}}}},{"type":"object","description":"Plain-string routing/model error.","properties":{"error":{"type":"string"}}}]}}}},"UnauthorizedError":{"description":"Invalid Credentials - Your API key is invalid, missing, or disabled. Check your credentials.\n\nNote: the 401 error body uses `code`, `message`, `param`, and `type` (there is no `status` field), and `type` is `invalid_request_error`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"type":"string"},"param":{"type":"string","nullable":true},"code":{"type":"string"}}}}}}}}}},"paths":{"/v1/live/sessions":{"get":{"operationId":"createLiveSession","tags":["Live Sessions"],"summary":"Create Live Session (WebSocket)","description":"Opens a WebSocket to a GPT-Live session for real-time voice (and text-context) conversations. FastRouter proxies the session to OpenAI, validates the model, and injects `cost` into the usage events so you can see what each session is charged.\n\n**Connection URL**\n```\nwss://api.fastrouter.ai/v1/live/sessions?model=openai/gpt-live-1\n```\n\n**Authentication** (checked before the upgrade): `Authorization: Bearer <key>` or `x-api-key: <key>` header, or `?api_key=<key>` in the URL for clients that cannot set WebSocket headers (browsers).\n\n**Models.** `model` (query, or `session.model` in `session.start`) is the *live voice model* — a FastRouter catalogue id such as `openai/gpt-live-1` (the bare `gpt-live-1` is also accepted). An unknown model is rejected with `400 model_not_found`. The *delegation model* (below) is a separate OpenAI text model.\n\n### Protocol\n\nMessages are JSON events in both directions. **The server sends nothing until you send `session.start`** — it must be your first message. Reply: `session.started` with the session object, including `session.id`.\n\n```json\n{\"type\": \"session.start\", \"event_id\": \"start-1\", \"session\": {\n  \"model\": \"openai/gpt-live-1\",\n  \"instructions\": \"You are a concise assistant.\",\n  \"audio\": {\"format\": {\"type\": \"audio/pcm\", \"rate\": 24000}, \"output\": {\"voice\": \"marin\"}},\n  \"store\": false,\n  \"delegation\": {\"type\": \"responses\", \"responses\": {\"model\": \"gpt-4o-mini\"}}\n}}\n```\n\n`session` fields: `model`, `instructions`, `audio` (`format.type`: `audio/pcm` at 24000 or 16000, `audio/pcmu` or `audio/pcma` at 8000; `output.voice`), `input` (seed text history: `[{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"...\"}]}]`), `delegation`, `store`.\n\n**Client events** (forwarded to OpenAI as-is; any other `type` — including OpenAI *Realtime* names such as `conversation.item.create` — is rejected with an `error` event):\n\n| Event | Payload | Notes |\n|---|---|---|\n| `session.start` | `{\"session\": {...}}` | First message. Fixes `delegation` for the session's lifetime. |\n| `session.update` | `{\"session\": {...}}` | Update instructions/audio. Cannot change `delegation.type`. |\n| `session.input_audio.append` | `{\"audio\": \"<base64>\"}` | Stream microphone audio continuously. There is no commit — GPT-Live decides when to reply. |\n| `session.input_audio.mute` / `.unmute` | `{}` | Pause/resume input while the session keeps running. |\n| `session.instructions.append` | `{\"content\": \"...\"}` | Add instructions mid-session. |\n| `session.thinking.append` | `{\"content\": \"...\"}` | Add private context the model should consider. |\n| `session.commentary.append` | `{\"content\": \"...\"}` | Add context the model may speak about. |\n| `response.item.create` | OpenAI Responses input item | Requires `delegation.type: \"responses\"`. |\n| `response.create` | `{}` or Responses request fields | Requires `delegation.type: \"responses\"`; runs a turn on the delegation model. |\n| `session.close` | `{}` | End the session; `session.closed` follows with final usage. |\n\nEvery event may carry `event_id`; acknowledgements and errors echo it as `client_event_id`.\n\n**Delegation.** `delegation.type` is `\"client\"` (default — the live model handles everything itself) or `\"responses\"` — delegated work (tool calls, `response.create` turns) runs on an OpenAI *Responses API* model. With `\"responses\"`, `delegation.responses.model` is required and is sent to OpenAI verbatim, so use OpenAI's bare id (e.g. `gpt-4o-mini`), not the `openai/` catalogue form. Optional `delegation.responses` fields: `instructions`, `tools`, `tool_choice`, `parallel_tool_calls`, `max_output_tokens`, `service_tier`, `reasoning`, `text`.\n\n**Server events**\n\n| Event | Payload | Notes |\n|---|---|---|\n| `session.started` / `session.updated` | `{\"session\": {\"id\": \"...\", ...}}` | `session.id` is the id used for forking. |\n| `session.output_audio.delta` | `{\"delta\": \"<base64>\"}` | Spoken reply, in the session's audio format. |\n| `session.input_transcript.delta` / `session.output_transcript.delta` | `{\"delta\": \"...\", \"start_ms\", \"end_ms\"}` | Transcripts of what was heard / said. |\n| `session.usage.updated` | `{\"usage\": {\"seconds\": 12.4, \"cost\": 0.0021}}` | Periodic snapshot (cumulative, not a delta). `cost` is added by FastRouter. |\n| `session.delegation.created` | `{\"delegation_id\": \"...\"}` | A delegated task started. |\n| `response.event` | `{\"delegation_id\": \"...\", \"event\": {...}}` | Wraps a nested OpenAI Responses event: `response.created`, `response.output_text.delta` (`event.delta` holds the text), `response.output_item.added/done`, `response.completed` (`event.response.usage` has tokens and a FastRouter-added `cost`). |\n| `session.*.appended`, `session.input_audio.muted/unmuted` | `{\"client_event_id\": \"...\"}` | Acknowledgements. |\n| `session.closed` | `{\"reason\": \"...\", \"usage\": {\"seconds\", \"cost\"}}` | Final usage. `reason` is e.g. `expired` when OpenAI's session lifetime is reached, or set by `session.close`. |\n| `error` | `{\"error\": {\"type\", \"code\", \"message\", \"param\", \"client_event_id\"}}` | Validation and upstream errors. |\n\n**Billing.** Voice time is billed per second of session audio at the live model's rate (`usage.seconds`); delegated responses are billed per token at the delegation model's rate. Both `cost` values are in FastRouter credits and appear on the events above. Session lifetime and idle limits are set by OpenAI — expect `session.closed` with `reason: \"expired\"` rather than an indefinite session.\n\n**Minimal client**\n```javascript\nconst ws = new WebSocket(\"wss://api.fastrouter.ai/v1/live/sessions?model=openai/gpt-live-1&api_key=\" + KEY);\nws.onopen = () => ws.send(JSON.stringify({type: \"session.start\", session: {\n  audio: {format: {type: \"audio/pcm\", rate: 24000}},\n  delegation: {type: \"responses\", responses: {model: \"gpt-4o-mini\"}}\n}}));\nws.onmessage = ({data}) => {\n  const ev = JSON.parse(data);\n  if (ev.type === \"session.started\") {\n    ws.send(JSON.stringify({type: \"response.item.create\", item: {type: \"message\", role: \"user\", content: [{type: \"input_text\", text: \"Say hello in one word.\"}]}}));\n    ws.send(JSON.stringify({type: \"response.create\"}));\n  }\n  if (ev.type === \"response.event\" && ev.event.type === \"response.output_text.delta\") process.stdout.write(ev.event.delta);\n  if (ev.type === \"response.event\" && ev.event.type === \"response.completed\") ws.send(JSON.stringify({type: \"session.close\"}));\n  if (ev.type === \"session.closed\") console.log(\"\\ncost:\", ev.usage.cost);\n};\n```\nA full browser client with microphone streaming is in `examples/live_client.html`.","parameters":[{"name":"model","in":"query","required":false,"schema":{"type":"string","default":"gpt-live-1"},"description":"Live voice model (FastRouter catalogue id, e.g. openai/gpt-live-1). Defaults to gpt-live-1. Can also be set via session.model in session.start."},{"name":"api_key","in":"query","required":false,"schema":{"type":"string"},"description":"FastRouter API key, for clients that cannot set WebSocket headers (browsers). Alternative to the Authorization / x-api-key headers."}],"responses":{"200":{"description":"WebSocket connection established. Send `session.start` first; see the endpoint description for the full client/server event protocol."},"400":{"$ref":"#/components/responses/BadRequestError"},"401":{"$ref":"#/components/responses/UnauthorizedError"}}}}}}
````

## Fork Live Session (WebSocket)

> Opens a WebSocket that forks a new GPT-Live session from a stored one, continuing its conversation.\
> \
> \*\*Connection URL\*\*\
> \`\`\`\
> wss\://api.fastrouter.ai/v1/live/sessions/{session\_id}/fork?model=openai/gpt-live-1\
> \`\`\`\
> \
> \*\*Authentication:\*\* same as \`/v1/live/sessions\` (\`Authorization: Bearer\`, \`x-api-key\`, or \`?api\_key=\`).\
> \
> \*\*What a stored session is.\*\* A session is stored by OpenAI when it was started with \`"store": true\` in \`session.start\`. Its id is the \`session.id\` from that session's \`session.started\` event. Retention is governed by OpenAI.\
> \
> \*\*Forking.\*\* Send \`session.start\` as the first message, exactly as for a new session. The fork inherits the source session's model, voice, instructions and conversation input, so \`session.model\` is ignored here (the \`model\` query parameter is still used for billing resolution); you may set a new \`delegation\`, and the fork's own \`store\` controls whether it can be forked again.\
> \
> \`\`\`json\
> {"type": "session.start", "session": {"store": true, "delegation": {"type": "responses", "responses": {"model": "gpt-4o-mini"}}}}\
> \`\`\`\
> \
> Events, delegation, billing and lifetime are identical to \`/v1/live/sessions\` — see that endpoint.

````json
{"openapi":"3.1.0","info":{"title":"FastRouter API Reference","version":"1.0.0"},"tags":[{"name":"Live Sessions","description":"GPT-Live WebSocket sessions API for real-time voice and text conversations using OpenAI's gpt-live models. Supports starting a new session and forking an existing stored session."}],"servers":[{"url":"https://api.fastrouter.ai","description":"Production API"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"FastRouter API Key. Get yours at https://fastrouter.ai\n\nFormat: `Authorization: Bearer YOUR_API_KEY`"}},"responses":{"BadRequestError":{"description":"Bad Request - The request is malformed. This could be due to missing parameters, invalid formats, or routing errors.\n\n**Note:** the 400 body shape is not uniform. Validation errors return the structured object below, but routing/model errors may instead return a plain string: `{ \"error\": \"<message>\" }`. A few endpoints (e.g. some file operations) may return a plain-text body rather than JSON.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","description":"Structured validation error.","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"type":"string"},"code":{"type":"string"}}}}},{"type":"object","description":"Plain-string routing/model error.","properties":{"error":{"type":"string"}}}]}}}},"UnauthorizedError":{"description":"Invalid Credentials - Your API key is invalid, missing, or disabled. Check your credentials.\n\nNote: the 401 error body uses `code`, `message`, `param`, and `type` (there is no `status` field), and `type` is `invalid_request_error`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"type":"string"},"param":{"type":"string","nullable":true},"code":{"type":"string"}}}}}}}}}},"paths":{"/v1/live/sessions/{session_id}/fork":{"get":{"operationId":"forkLiveSession","tags":["Live Sessions"],"summary":"Fork Live Session (WebSocket)","description":"Opens a WebSocket that forks a new GPT-Live session from a stored one, continuing its conversation.\n\n**Connection URL**\n```\nwss://api.fastrouter.ai/v1/live/sessions/{session_id}/fork?model=openai/gpt-live-1\n```\n\n**Authentication:** same as `/v1/live/sessions` (`Authorization: Bearer`, `x-api-key`, or `?api_key=`).\n\n**What a stored session is.** A session is stored by OpenAI when it was started with `\"store\": true` in `session.start`. Its id is the `session.id` from that session's `session.started` event. Retention is governed by OpenAI.\n\n**Forking.** Send `session.start` as the first message, exactly as for a new session. The fork inherits the source session's model, voice, instructions and conversation input, so `session.model` is ignored here (the `model` query parameter is still used for billing resolution); you may set a new `delegation`, and the fork's own `store` controls whether it can be forked again.\n\n```json\n{\"type\": \"session.start\", \"session\": {\"store\": true, \"delegation\": {\"type\": \"responses\", \"responses\": {\"model\": \"gpt-4o-mini\"}}}}\n```\n\nEvents, delegation, billing and lifetime are identical to `/v1/live/sessions` — see that endpoint.","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string"},"description":"Id of the stored session to fork: the `session.id` from its `session.started` event (the source must have been started with `store: true`)."},{"name":"model","in":"query","required":false,"schema":{"type":"string","default":"gpt-live-1"},"description":"Live voice model for billing resolution (the fork inherits the source session's model). Defaults to gpt-live-1."},{"name":"api_key","in":"query","required":false,"schema":{"type":"string"},"description":"FastRouter API key, for clients that cannot set WebSocket headers (browsers). Alternative to the Authorization / x-api-key headers."}],"responses":{"200":{"description":"WebSocket connection established. Send `session.start` first; see the endpoint description for the full client/server event protocol."},"400":{"$ref":"#/components/responses/BadRequestError"},"401":{"$ref":"#/components/responses/UnauthorizedError"}}}}}}
````


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.fastrouter.ai/api-reference/sessions.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
