Connect to an existing conversation over WebSocket for realtime voice and text interactions and background work events.
Realtime is currently in beta. API definitions, event structures, and behavior may change. Monitor documentation updates and validate compatibility before using it in production.
GET /api/v1/forward/realtime
A Realtime connection can remain open for up to 60 minutes.
Headers
| Header | Required | Description |
|---|---|---|
| Authorization | Yes | Bearer <PAT or SAT> |
| Upgrade | Yes | websocket, generated by the WebSocket client. |
| Connection | Yes | Upgrade, generated by the WebSocket client. |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| conversation_id | string | Yes | ID of a conversation that is accessible with the current credentials and has a status of ready. |
Example request
wss://api.qoder.com/api/v1/forward/realtime?conversation_id=conv_xxx
Example response
HTTP 101 Switching Protocols
After the WebSocket connection is ready, the server sends voice.ready:
voice.ready.payload fields
For common event fields, see Realtime events.
| Field | Type | Description |
|---|---|---|
| config | object | Conversation configuration effective for the current connection. |
| config.audio.output.voice | string | Voice identifier effective for the current connection. |
| provider | string | Service provider. This is always qoder in the public environment. |
| input_audio | object | Audio format used by audio.append. |
| input_audio.format | string | Always pcm16: raw PCM, little-endian signed 16-bit, without a WAV header. |
| input_audio.sample_rate | integer | Always 16000 Hz. |
| input_audio.channels | integer | Always 1, mono. |
| output_audio | object | Audio format used by audio.delta. |
| output_audio.format | string | Always pcm16, encoded in the same way as input audio. |
| output_audio.sample_rate | integer | Always 24000 Hz. |
| output_audio.channels | integer | Always 1, mono. |
| capabilities | object | Protocol capabilities supported by the current connection. |
| capabilities.graceful_close | boolean | Whether the graceful close protocol is supported. Currently true. |
Errors
Before the handshake completes, errors are returned as HTTP responses:
| HTTP | Code | Trigger |
|---|---|---|
| 400 | conversation_id_required, unsupported_query_parameter | A WSS query parameter is missing or unsupported. |
| 401 | authentication_required, gateway TOKEN_INVALID | The PAT or SAT is invalid or expired, or a Service Account Key was used directly. |
| 403 | permission_error | The SAT is not bound to an available Workspace. |
| 404 | conversation_not_found | The Conversation does not exist, or the current credentials cannot access its associated user, Workspace, or Identity. |
| 409 | conversation_not_ready | The conversation is not ready. |
| 500 | conversation_persistence_error | An internal service error occurred. |
| 502 | forward_unavailable, forward_protocol_error | A dependent service failed. |
| 503 | gateway_unavailable | The service is temporarily unavailable. |
error events. See WebSocket errors.
Realtime events
Events are transmitted as UTF-8 JSON text messages. Binary messages are not accepted. A client message can be up to 256 KiB, including JSON and Base64. Send audio or text only after receiving voice.ready.
Common fields
| Field | Type | Description |
|---|---|---|
| version | string | Required in both directions. Always voice.realtime.v1. |
| type | string | Required in both directions. Event type. |
| payload | object | Required in both directions. Event data; use {} when there are no business fields. |
| event_id | string | Always returned by the server. This is the current WSS delivery ID, not a historical event ID. Clients do not send this field. |
| sequence | integer | Always returned by the server. Increases within the current connection, resets after reconnecting, and is not a resume cursor. |
| conversation_id | string | Always returned by the server. The associated conversation. |
| timestamp | string | Always returned by the server. RFC 3339 time, which may include fractional seconds with nanosecond precision. |
| work_id | string | Optional server field. The associated work item. |
| announcement_id | string | Optional server field. The associated announcement. |
Client events
| type | Payload fields | Description |
|---|---|---|
audio.append | audio: string | Send non-empty Base64 PCM16 chunks. See voice.ready.payload.input_audio for the encoding. The server detects voice activity; no commit event is required. |
text.message | text: string | Non-empty after trimming and at most 16 KiB in UTF-8. |
interrupt | None | Interrupt the current response. The client stops playback and clears its queue. |
playback.started | work_id: string, announcement_id: string; required when present on the corresponding audio event. | Playback actually started. Echo the top-level identifiers from the corresponding audio event. Send {} when no identifiers are present. |
playback.ended | work_id: string, announcement_id: string; required when present on the corresponding audio event. | The current audio segment finished playing and the queue is empty. |
playback.cancelled | work_id: string, announcement_id: string; required when present on the corresponding audio event. | Playback could not start or was cancelled. When interrupted, stop playback, clear the queue, and send this receipt with the original audio identifiers. |
ping | None | Health check. The response is pong. |
connection.close | request_id: string | Send after stopping capture and playback, then wait for connection.closed with the same request_id. The identifier must be non-empty and at most 128 bytes, with no leading or trailing whitespace, newline, or null character. |
Server events
For an event structure example, see Example response.
| type | Payload fields | Description |
|---|---|---|
voice.state | state: string | connecting, ready, idle, or interrupted. |
voice.ready | config: object, provider: string, input_audio: object, output_audio: object, capabilities: object | The connection is ready. Returns the effective conversation configuration and audio transport formats. |
voice.replaced | reason: string | The connection was replaced. |
transcript.delta | role: string, text: string; optional item_id: string, response_id: string | User transcription updates or assistant caption deltas. |
transcript.final | Same as above | Final caption that replaces temporary captions. |
audio.delta | audio: string | Base64 PCM16 audio. See voice.ready.payload.output_audio for the encoding. |
audio.done | None | Output of the current audio segment is complete. This does not mean client playback is complete. |
playback.interrupt | reason: string | Stop playback and clear the queue. |
work.accepted | status: "accepted", objective: string | The work item was accepted. |
work.running | status: "running" | The work item is running. |
work.progress | kind: string, detail: string; optional title: string, is_error: boolean, event_id: string | Work progress. The payload event_id is the persisted Forward event ID. |
work.milestone | announcement_id: string, milestone_type: string, summary: string | Work milestone information. |
work.completed | status: "completed", result: string; optional event_id: string | The work item completed. Announcement playback may still be in progress. The payload event_id is the persisted Forward event ID. |
work.failed | status: "failed", error.code: string; optional event_id: string | The work item failed or was cancelled independently of connection state. The payload event_id is the persisted Forward event ID. |
pong | None | Response to ping. |
connection.closed | request_id: string, outcome: string | Close confirmation that returns the original request_id. outcome is no_active_response, no_content, saved, or saved_interrupted. |
error | code: string, message: string; optional retryable: boolean, retry_after_ms: integer | Protocol or service error. |
WebSocket errors
| Code | Closes connection | Trigger |
|---|---|---|
invalid_event, text_too_long | No | The event is invalid or the text limit was exceeded. |
voice_not_ready | No | The Realtime connection is not ready. |
invalid_playback_receipt | No | The playback receipt is invalid. |
connection_closing | No | The connection is closing and no longer accepts business events. |
invalid_work_request, work_busy, work_unavailable, work_persistence_error, forward_request_failed, forward_cancel_failed, forward_protocol_error | No | The work request or execution failed. |
provider_initialization_failed, voice_configuration_failed, context_restore_failed | Yes | Realtime model initialization, voice confirmation, or context restoration failed. |
provider_unavailable, service_restarting | Yes | The service is unavailable. Reconnect after retry_after_ms when provided. |
event_persistence_failed | Yes | Event persistence failed and the last historical event might be missing. |
HTTP error response
Response fields
| Field | Type | Description |
|---|---|---|
| type | string | Always error. |
| request_id | string | Request trace ID. |
| error.type | string | Error category, such as invalid_request_error, authentication_error, permission_error, not_found_error, conflict_error, or api_error. |
| error.code | string | Stable business error code. |
| error.message | string | Error description. |

