Skip to main content
Realtime

Connect to a conversation

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

HeaderRequiredDescription
AuthorizationYesBearer <PAT or SAT>
UpgradeYeswebsocket, generated by the WebSocket client.
ConnectionYesUpgrade, generated by the WebSocket client.

Query parameters

ParameterTypeRequiredDescription
conversation_idstringYesID 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
GET /api/v1/forward/realtime?conversation_id=conv_xxx HTTP/1.1
Host: api.qoder.com
Authorization: Bearer <PAT or SAT>
Upgrade: websocket
Connection: Upgrade

Example response

HTTP 101 Switching Protocols After the WebSocket connection is ready, the server sends voice.ready:
{
  "version": "voice.realtime.v1",
  "type": "voice.ready",
  "event_id": "evt_xxx",
  "sequence": 2,
  "conversation_id": "conv_xxx",
  "timestamp": "2026-08-31T02:00:01Z",
  "payload": {
    "config": {
      "audio": {
        "output": {
          "voice": "longanlingxin"
        }
      }
    },
    "provider": "qoder",
    "input_audio": {
      "format": "pcm16",
      "sample_rate": 16000,
      "channels": 1
    },
    "output_audio": {
      "format": "pcm16",
      "sample_rate": 24000,
      "channels": 1
    },
    "capabilities": {
      "graceful_close": true
    }
  }
}

voice.ready.payload fields

For common event fields, see Realtime events.
FieldTypeDescription
configobjectConversation configuration effective for the current connection.
config.audio.output.voicestringVoice identifier effective for the current connection.
providerstringService provider. This is always qoder in the public environment.
input_audioobjectAudio format used by audio.append.
input_audio.formatstringAlways pcm16: raw PCM, little-endian signed 16-bit, without a WAV header.
input_audio.sample_rateintegerAlways 16000 Hz.
input_audio.channelsintegerAlways 1, mono.
output_audioobjectAudio format used by audio.delta.
output_audio.formatstringAlways pcm16, encoded in the same way as input audio.
output_audio.sample_rateintegerAlways 24000 Hz.
output_audio.channelsintegerAlways 1, mono.
capabilitiesobjectProtocol capabilities supported by the current connection.
capabilities.graceful_closebooleanWhether the graceful close protocol is supported. Currently true.

Errors

Before the handshake completes, errors are returned as HTTP responses:
HTTPCodeTrigger
400conversation_id_required, unsupported_query_parameterA WSS query parameter is missing or unsupported.
401authentication_required, gateway TOKEN_INVALIDThe PAT or SAT is invalid or expired, or a Service Account Key was used directly.
403permission_errorThe SAT is not bound to an available Workspace.
404conversation_not_foundThe Conversation does not exist, or the current credentials cannot access its associated user, Workspace, or Identity.
409conversation_not_readyThe conversation is not ready.
500conversation_persistence_errorAn internal service error occurred.
502forward_unavailable, forward_protocol_errorA dependent service failed.
503gateway_unavailableThe service is temporarily unavailable.
After the handshake completes, errors are returned through 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

FieldTypeDescription
versionstringRequired in both directions. Always voice.realtime.v1.
typestringRequired in both directions. Event type.
payloadobjectRequired in both directions. Event data; use {} when there are no business fields.
event_idstringAlways returned by the server. This is the current WSS delivery ID, not a historical event ID. Clients do not send this field.
sequenceintegerAlways returned by the server. Increases within the current connection, resets after reconnecting, and is not a resume cursor.
conversation_idstringAlways returned by the server. The associated conversation.
timestampstringAlways returned by the server. RFC 3339 time, which may include fractional seconds with nanosecond precision.
work_idstringOptional server field. The associated work item.
announcement_idstringOptional server field. The associated announcement.

Client events

{
  "version": "voice.realtime.v1",
  "type": "text.message",
  "payload": {
    "text": "Hello. Please briefly introduce yourself."
  }
}
typePayload fieldsDescription
audio.appendaudio: stringSend 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.messagetext: stringNon-empty after trimming and at most 16 KiB in UTF-8.
interruptNoneInterrupt the current response. The client stops playback and clears its queue.
playback.startedwork_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.endedwork_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.cancelledwork_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.
pingNoneHealth check. The response is pong.
connection.closerequest_id: stringSend 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.
typePayload fieldsDescription
voice.statestate: stringconnecting, ready, idle, or interrupted.
voice.readyconfig: object, provider: string, input_audio: object, output_audio: object, capabilities: objectThe connection is ready. Returns the effective conversation configuration and audio transport formats.
voice.replacedreason: stringThe connection was replaced.
transcript.deltarole: string, text: string; optional item_id: string, response_id: stringUser transcription updates or assistant caption deltas.
transcript.finalSame as aboveFinal caption that replaces temporary captions.
audio.deltaaudio: stringBase64 PCM16 audio. See voice.ready.payload.output_audio for the encoding.
audio.doneNoneOutput of the current audio segment is complete. This does not mean client playback is complete.
playback.interruptreason: stringStop playback and clear the queue.
work.acceptedstatus: "accepted", objective: stringThe work item was accepted.
work.runningstatus: "running"The work item is running.
work.progresskind: string, detail: string; optional title: string, is_error: boolean, event_id: stringWork progress. The payload event_id is the persisted Forward event ID.
work.milestoneannouncement_id: string, milestone_type: string, summary: stringWork milestone information.
work.completedstatus: "completed", result: string; optional event_id: stringThe work item completed. Announcement playback may still be in progress. The payload event_id is the persisted Forward event ID.
work.failedstatus: "failed", error.code: string; optional event_id: stringThe work item failed or was cancelled independently of connection state. The payload event_id is the persisted Forward event ID.
pongNoneResponse to ping.
connection.closedrequest_id: string, outcome: stringClose confirmation that returns the original request_id. outcome is no_active_response, no_content, saved, or saved_interrupted.
errorcode: string, message: string; optional retryable: boolean, retry_after_ms: integerProtocol or service error.

WebSocket errors

CodeCloses connectionTrigger
invalid_event, text_too_longNoThe event is invalid or the text limit was exceeded.
voice_not_readyNoThe Realtime connection is not ready.
invalid_playback_receiptNoThe playback receipt is invalid.
connection_closingNoThe 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_errorNoThe work request or execution failed.
provider_initialization_failed, voice_configuration_failed, context_restore_failedYesRealtime model initialization, voice confirmation, or context restoration failed.
provider_unavailable, service_restartingYesThe service is unavailable. Reconnect after retry_after_ms when provided.
event_persistence_failedYesEvent persistence failed and the last historical event might be missing.

HTTP error response

{
  "type": "error",
  "request_id": "req_xxx",
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_idempotency_key",
    "message": "Idempotency-Key is required and must be at most 256 characters"
  }
}

Response fields

FieldTypeDescription
typestringAlways error.
request_idstringRequest trace ID.
error.typestringError category, such as invalid_request_error, authentication_error, permission_error, not_found_error, conflict_error, or api_error.
error.codestringStable business error code.
error.messagestringError description.
The server also returns the request trace ID in a response header:
X-Request-Id: req_xxx
Gateway authentication errors use the following structure (HTTP 401):
{
  "code": "TOKEN_INVALID",
  "message": "missing authorization token"
}
A proxy or standard WebSocket handshake failure may return a non-JSON response.
Best Practices
API reference
Connect to a conversation - Qoder