Use the generated realtime reference for a message index or download the AsyncAPI contract.
WebSocket API
Connect to wss://api.dialt.com/v1/realtime. Trusted non-browser clients may authenticate with Authorization: Bearer dk_…; existing ck_… keys remain valid. All clients may include api_key in their first JSON frame.
Connection and message limits
Send your complete first JSON text message immediately after connecting. Receiving it and verifying its credential must finish within 10 seconds after the WebSocket upgrade, including when you supply a Bearer header. Expiry closes with code 1008 and reason authentication timeout. Pings and partial messages do not extend this deadline. Stalled pre-authentication output is forcibly disconnected after at most 2 additional seconds and may appear as an abnormal close. The HTTP opening handshake has a separate timeout; authenticated sessions retain their normal duration.
Every incoming message, including start, tool results, controls and binary audio, has a 2 MiB (2,097,152-byte) limit. Count UTF-8 bytes after JSON serialization and the total across all WebSocket fragments. Oversized messages close with 1009. Existing field limits and tool-result truncation still apply. Keep large tool schemas, history and results within this envelope; split larger audio buffers into the recommended paced 20–100 ms messages. Server-to-client audio and WAV responses are unaffected.
Before authentication completes, a connection accepts at most 4,096 data frames cumulatively across messages, including empty continuations and final fragments. Exceeding this budget closes with 1009, even below the byte limit. Wait for ready before sending session traffic. Authenticated sessions do not inherit this frame-count budget.
WebSocket compression is not negotiated. Clients offering optional compression continue uncompressed. If pre-authentication capacity is full, the upgrade returns HTTP 503; retry with backoff. Existing SDKs send the start message immediately. Raw integrations that previously delayed their first message or sent messages larger than 2 MiB must adjust before adopting these limits.
Start frame
{
"type": "start",
"session_id": "your-unique-session-id",
"api_key": "dk_or_scoped_key",
"audio": { "sr": 16000 },
"mode": {
"kind": "dialt",
"instructions": "You are a helpful voice assistant on a phone call. If a request is missing a key detail, ask one concise clarifying question.",
"web_search": false,
"end_call": false
},
"client": {
"user": "optional-stable-user-id",
"timezone": "Europe/Dublin",
"capabilities": []
}
}
| Field | Required | Description |
|---|---|---|
type | Yes | Must be start. |
session_id | Yes | Your 1–64 character correlation ID, new for each call; must match a scoped key. |
api_key | If no auth header | Persistent or scoped credential. |
audio.sr | Voice only | Input sample rate. Use 16000. Omit the entire audio object for text. |
mode | Yes | Conversation configuration. |
mode.instructions | Yes, on a fresh start | The agent's role: who it is, whose side it is on, what it knows and what it should do on this call. See Assistant instructions. A resume inherits the session's instructions. |
client | No | User metadata and implemented protocol capabilities. |
For text mode, set mode.modality to "text" and omit audio. Text mode uses the WebSocket transport only. The model, instructions, tools, greeting, history and event lifecycle are unchanged; only the media pipeline is absent.
Multi-speaker listening (experimental)
Isolated participant inputs are an internal Dialt-organization pilot; general availability is not offered. Set start.mode.multispeaker: true, top-level participants: [{ id: "alice", name: "Alice" }] and audio.uplink_format: "participant-pcm16-v1". Voice, WebSocket and server turn detection are required. Supply one to four isolated mono inputs; a mixed microphone cannot provide participant identity. IDs must be unique 1–64 ASCII letters, digits, underscores or hyphens; optional names have at most 100 characters. The roster is session-scoped and is never saved on a Design.
Each binary frame has a 12-byte little-endian 2sBBII header: magic VL, version 2, zero-based roster index, independent per-line uint32 sequence and uint32 source capture milliseconds. Follow it with nonempty even-sized mono PCM16 at the session sample rate, at most 48000 payload bytes (48012 including the header). Unknown indexes, wrong versions and invalid lengths fail closed. Capture clocks are independent, not proof of synchronized devices. Resume inherits the exact roster, including names; changes are rejected.
participant_id identifies source-line provenance in voice asr, asr_correction and native user conversation messages, including imported history. It does not authenticate a person or authorize tools. Server-native assistant role remains authoritative; instructions may configure the assistant's name. The directed-speech classifier returns only ASSISTANT, OTHER or UNKNOWN, with no MIXED label or clause splitting. For mixed-address speech admitted as ASSISTANT, the normal response model interprets its own request under ordinary tool permissions.
Current limitations: provider endpoints only, with single-mic local VAD/silence acceleration, activity and protective barge sensors bypassed. Endpoint-policy parity is unqualified; routes requiring local finalization may stall. Spelling audio and transcribe_latest_turn are disabled. Reset requires a new session; raw-audio and client turn-end controls are unsupported. A lost line gets one reconnect attempt without replay; repeated loss terminates the call. Caller speculation, automatic idle nudges, successful-silence requery and compaction are disabled. Hosted evals are unsupported. The classifier can miss requests or answer speech meant for another participant. Recording ingestion, separate-track playback and downloads are supported; automatic participant alignment and review are unsupported. Broader multilingual, overlapping-speech and sustained-load behavior remain unqualified.
This is currently a private Dialt-organization pilot. Other organizations and personal account contexts receive nonretryable permission_denied; membership or a team role does not enable it outside the authorized organization context. Playground shows the toggle only when the active account is eligible. Opted-in sessions check listening-service readiness before ready; warming or unavailable dependencies return retryable pipeline_unavailable. Keep the client in its connecting state until ready, and surface a temporary-service error if startup fails. Ordinary sessions are unaffected.
The setting is immutable for the logical session. Reconnect and resume retain it: omitting the field on resume inherits the saved value, while explicitly changing either true to false or false to true is rejected. Start a new session to change it. This is a per-session API opt-in; no environment enable flag is needed.
Language switching (experimental)
Experimental multilingual conversations: set start.mode.multilingual: true alongside mode.voice to enable multilingual conversation in Dialt voice or text sessions for every available voice. It defaults to false. Language quality varies by voice and is not guaranteed for arbitrary languages. Prerecorded tool bridges are disabled, including tools declaring recorded_bridge: "always"; natural model-authored speech before tool calls is preserved. This setting is fixed for the session and retained across reconnect and resume. Changing it requires a new session; there is no mid-session control or per-language option. The playground's Multilingual (experimental) toggle applies to the next call. Existing integrations can leave it omitted.
With multilingual off, the selected voice's default-language guidance and configured language hints remain active. For example, an Egyptian Arabic voice supplies Egyptian Arabic guidance even if your instructions ask it to switch to English. Instructions alone do not change that configuration. For Arabic/English calls, enable multilingual on a fresh session and remove conflicting application rules such as Speak Arabic only. With multilingual on, guidance follows the caller's latest language or explicit request; before caller speech it uses the voice's default language unless your instructions specify another opening language. Validate recognition, generated wording and spoken pronunciation on the exact selected voice in both languages and across switches; preserving a voice identity does not guarantee its quality in another language.
Brain tier
mode.brain selects the conversational model tier. "fast" is the default and suits most calls. "smart" uses a stronger model that chooses tools more accurately but replies more slowly, and it is billed at the smart rate.
Assistant instructions
start.mode.instructions is required. These developer instructions supply the application role, goals, knowledge, policy and task behavior. For example: You are the customer support assistant for [company]. Help customers with [company] products and accounts. If a request is missing a key detail, ask one concise clarifying question.
Dialt's platform prompt combines platform rules (speech output, conversation mechanics and capabilities), selected voice guidance, and call facts from runtime session context. Platform rules are not customer-configurable. Your developer instructions share the assembled system message with this content; tool-use rules follow them when tools are declared. These ownership boundaries do not guarantee model obedience or prevent arbitrary developer text from influencing behavior. Validate the behavior your application depends on.
mode.voice selects a synthesis identity with default pronunciation/accent and coupled voice guidance. mode.multilingual selects its experimental multilingual guidance. The voice guidance text covers language/dialect, register, grammatical agreement, script and delivery. Register means language suited to a social situation, such as formal or informal wording; dialect spans pronunciation and wording. Speaking style includes wording (vocabulary, grammar, phrasing, formality and verbosity) and delivery (pace, pauses, emphasis, intonation and energy).
Voice supplies defaults; developer instructions supply the application role and may request application-specific wording and delivery within the platform contract. Prompting guides wording and delivery indirectly through generated text; speech synthesis receives that text and voice/settings, not the assembled system prompt. Check both the generated wording and the resulting audio. There are no independent customer style, accent or delivery settings, or customer fine-tuning controls today.
Replace [company] with your organization. Dialt does not impose a Dialt identity or secrecy policy. A general default is You are a helpful voice assistant on a phone call. If a request is missing a key detail, ask one concise clarifying question.
A fresh start without non-empty instructions is refused with a non-retryable error, and the socket closes with code 1008:
{
"type": "error",
"code": "missing_instructions",
"retryable": false,
"detail": "instructions are required: set mode.instructions to who the agent is, whose side it is on, what it knows and what it should do on this call"
}
A start that carries a resume_token inherits the session's instructions and does not need them. set_instructions with empty text fails with invalid_instructions and keeps the current instructions, and continue_with_agent requires non-empty instructions. Voice selection does not replace developer instructions.
If implementation questions matter to your application and you lack verified configuration, you may add: You haven't been given this deployment's underlying model or provider configuration. This is optional, not a default instruction.
Compose application instructions
Keep one authoritative home for each rule or fact. Compose shared application instructions with the current stage's instructions once; include each script or product description once. Avoid copying the same script into both background context and a call objective, or repeating grounding rules throughout a manual.
| Content | Where it belongs |
|---|---|
| Application identity, goals and enduring constraints | Shared developer instructions in mode.instructions. |
| Current stage's objective, approved wording and completion conditions | Active-stage developer instructions, composed with the shared instructions. |
| Tool calling conditions, prerequisites, effects and result guarantees | The tool's description and parameter schema. |
| Customer status, completed actions and other verified runtime facts | Tool results or acknowledged inject_context with role: "context". Distinguish pending work from verified completion. |
| Detailed FAQs and changing product facts | Your application's retrieval or business tools. Dialt does not automatically create a knowledge base from instructions. |
| Completed dialogue from an earlier conversation | initial_history on a fresh session; see conversation initialization. |
Keep essential approved wording and facts needed on every turn readily available. Use set_instructions to replace instructions for a phase change at a safe reply boundary. For a change of agent, use continue_with_agent to initialize instructions and tools atomically; when a tool result is the boundary, use tool_result.continue_with. See control frames and the Python or browser methods. Design-backed sessions pin their configuration and require a new session to change instructions.
Check for contradictory rules, including language-switching requests combined with language-only restrictions, or factual caveats contradicted by response scripts. Match language configuration to the intended conversation. Length is a diagnostic, not a quality threshold: context still contributes to model input, retrieval adds a tool round, and saving instructions in a Design does not shorten them. Compare representative scenarios before rollout using Evals; check factual accuracy, tool use, stage transitions, language switches and the resulting audio. Do not infer a latency improvement from fewer characters alone.
Audio frames
| Direction | Encoding | Recommended frame size |
|---|---|---|
| Client → Dialt | Binary PCM16 little-endian, mono, 16 kHz | 20–100 ms, paced in real time |
| Dialt → client | Binary PCM16 little-endian, mono, 16 kHz by default; request 8, 16 or 24 kHz with start.audio.output_sr. Raw clients may opt into pcm_f32le with output_encoding. | Play in arrival order |
The ready frame states the selected downlink format explicitly in its audio field (e.g. {"output_encoding": "pcm16", "output_sr": 16000}). Assert on it rather than assuming an encoding. The server guarantees a requested supported rate; a mismatch is a startup error. The browser and Python SDKs convert downlink audio and hand you Float32 samples with the selected rate; the wire itself carries PCM16 unless a raw client opts into pcm_f32le.
Treat ready as the authoritative initial session state, including the selected voice and negotiated audio format, rather than an echo of your request. Record those values for diagnosis. Keep resume_token private and replace it after a successful resume. Later acknowledged configuration changes update that initial state.
ready also carries an optional session_uuid, the server-minted session identifier: unchanged across a successful resume or broker-instance transfer, and different from session_id, which is your own correlation value. It is an identifier, not a credential; do not send it back.
JSON and binary frames share the same socket. A normal reply is turn, zero or more binary audio frames, utterance, then done. User transcripts arrive as asr.
The agent can end a session only when the start frame set mode.end_call: true. That declares the managed tool end_call(farewell), listed in tools_config like web_search. When the agent calls it, the turn sends one goodbye (utterance, then done). Reply text in that turn that ends a sentence is the goodbye, and farewell is not sent after it. Reply text that stops mid-sentence is followed by farewell, which completes it. With no reply text, farewell is the goodbye. Text the agent streamed before calling end_call follows the same rule, and farewell is sent if none of that text was heard. Advisory session_end_requested follows, carrying the farewell argument. Neither frame confirms that the peer heard or acted on the farewell. Headless relays should stop automatic new-turn injection but preserve queued output, and should not close solely for the advisory. The server then waits a short grace period (3 s by default) in which a real user turn cancels the end, and otherwise closes with code 1000 and reason ended by model. The grace is server timing, not a playback guarantee; clients own any local playback drain after normal close. Without the flag the agent cannot end the session: the host ends it with the wrap_up control frame or by closing. The name end_call is reserved while enabled, like web_search; a client tool by that name is then rejected with invalid_tools.
Client control frames
| Frame | Purpose |
|---|---|
{"type":"input_text","text":"Hello"} | Commit one user turn in text mode. |
{"type":"set_voice","voice":"key"} | Change voice for the next reply. |
{"type":"set_instructions","instructions":"…","new_speaker":true?} | Replace the session instructions for the next reply. new_speaker remains available for an independent speaker change and is refused with instructions_busy while a reply is in flight. |
{"type":"continue_with_agent","operation_id":"…","instructions":"…","tools":[…],"voice":"key"?,"context":"…"?,"tool_choice":…?,"tool_choice_one_shot":true?} | Atomically continue with initialized agent instructions, tools and optional voice, context and tool choice at a safe boundary. One continuation may be pending; receive handoff_ack as queued, then applied or rejected within 10 seconds. It does not start speech. |
{"type":"tool_result","id":"…","content":{…},"continue_with":{…}?} | Resolve a tool call. When its result is the agent boundary, include continue_with to commit the result, initialize the next agent and request one next-agent reply atomically. |
{"type":"tool_deferred","id":"…","handle":"…"} | Detach an eligible call from its voice turn. |
{"type":"tool_progress","id":"…","note":"…"} | Report non-final tool progress. |
{"type":"tool_cancel","id":"…"} | Cancel tool work. |
{"type":"client_event","event":"playback_stopped",…} | Report actual speaker playback after an interruption. |
{"type":"client_event","event":"playback_report",…} | Per-reply playback health: underruns, starved_ms, max_gap_ms and optional browser event_age_ms. |
Use a fresh operation_id for each continuation. While one is pending, a second is rejected. The broker waits only for a safe boundary: outstanding business tools, permissions and interactions must be completed or cancelled first. It never retries the operation, restores one after reconnect, or changes configuration on rejection. After handoff_ack reports applied, separately send acknowledged inject_context with role: "context" and reply: true when the initialized agent should speak without a caller turn. A caller turn wins any race with that request. handoff_agent remains a compatibility alias.
When the active tool result is the boundary, put the same fields under tool_result.continue_with. Dialt validates the result reference and complete next-agent configuration before changing anything. If accepted, it finishes any model-authored pre-tool sentence, prevents the old agent from answering the result, folds the result into prior-agent history, applies the new configuration and requests one new-agent reply. Optional tool_choice is validated against the incoming tools; set tool_choice_one_shot: true to constrain the incoming reply and restore auto afterward. If old-agent post-result audio is already audible or sibling work remains, handoff_ack rejects the operation and the tool call stays unresolved. Declare that transition tool with recorded_bridge: "never" when silence is preferable to Dialt's prerecorded latency cover. The existing handoff result field remains a compatibility alias.
Normal audio remains FIFO: the outgoing reply completes in the broker's stream before the specialist can begin. A real caller barge-in interrupts the outgoing reply and takes priority. The protocol does not provide literal hardware-speaker completion feedback, so it makes no stronger playout guarantee.
Explicit voice input boundaries
Set mode.turn_detection: "client" in the start frame to commit each voice turn yourself. After sending that turn's audio, send {"type":"client_event","event":"input_audio_done"}. The default, "server", detects turns automatically and ignores this control. In text mode use input_text.
To request a graceful sign-off in either modality, send {"type":"wrap_up","reason":"time_limit"}. The optional reason defaults to time_limit and is truncated to 40 characters. This works without mode.end_call enabled. There is no dedicated acknowledgement; continue handling reply events and closure, and drain local playback.
A later asr_correction replaces the earlier user transcript identified by its optional turn_id; do not append a duplicate turn. Optional policy revision fields identify an updated policy transcript. If no turn ID is supplied, do not invent one.
Maximum detected pause
mode.max_pause_s configures the silence fallback for server-detected voice turns. Set a positive finite number of seconds, or omit it to keep the default 6.
This is a fallback ceiling on continuously detected silence after speech. Earlier endpoint releases remain possible, including positive completion decisions. It is neither a minimum thinking pause nor a deadline for reply generation. Missing microphone delivery does not count as silence.
{
"mode": {
"kind": "dialt",
"instructions": "Help callers with their orders.",
"max_pause_s": 6
}
}
Text sessions and turn_detection: "client" reject a maximum-pause override with invalid_start_frame. The SDKs retain the option across reconnects; omission on resume inherits the session's value, and an explicit value replaces it. Reconnect starts a fresh silence observation clock. Existing integrations keep the six-second default without a migration.
Requests on a fresh connection
Authentication probes, feedback and client failure reports use the same WebSocket URL but a separate connection. Send one of the following as the first frame instead of start. Supply api_key in the frame or a Bearer handshake header. The response is {"type":"ok"} or the normal error frame, then the connection ends. These requests do not emit ready or consume a conversation slot.
| First frame | Meaning |
|---|---|
{"type":"auth","api_key":"<key>"} | Validate a credential. Success does not guarantee credit or available conversation capacity. |
{"type":"feedback","session_id":"call-123","rating":"up"} | Submit feedback for an owned recording. Supply rating or nonblank text. Re-submission replaces prior feedback. |
{"type":"client_error","detail":"Microphone permission denied"} | Report a client failure. Optional session_id must refer to an owned recording. Omit it for failures before session creation. Send diagnostics only, never credentials or conversation text. |
See the realtime reference for every field and operation. Raw capture and audio-frontend telemetry are SDK diagnostics, not application conversation controls.
Playback obligation
Clients that play assistant audio must track queued and discarded audio. After interrupted, report how much audio was not heard:
{
"type": "client_event",
"event": "playback_stopped",
"remaining_ms": 150,
"discarded_ms": 420,
"barge_seq": 3
}
client.audio_frontend is optional diagnostic metadata describing the active microphone/AEC path; use unknown until it is known.
Echo barge_seq from the interruption. Dialt may respond with a corrected utterance carrying the same sequence; replace the earlier assistant transcript for that interruption. The browser SDK handles this playback protocol automatically. Python and raw WebSocket clients must implement it when they perform real playback.
Client tools
Declare tools in mode.tools. Tools are available in Dialt mode and may be combined with web_search. The model chooses whether to answer directly, search, or call one or more client tools.
"tools": [{
"name": "lookup_order",
"description": "Look up an order by its customer-visible ID.",
"parameters": {
"type": "object",
"properties": { "order_id": { "type": "string" } },
"required": ["order_id"]
},
"read_only": true,
"requires_permission": false,
"recorded_bridge": "auto",
"status_label": "order lookup",
"timeout": 30,
"deferred": true,
"deferred_timeout": 7200,
"notify_on_complete": true
}]
| Field | Default | Contract |
|---|---|---|
name | Required | Unique, non-empty tool name. |
description | "" | Tells the model when and how to use the tool. |
parameters | Empty object schema | JSON Schema for args. |
read_only | false | true lets Dialt start the call early for speed. Use it only for harmless, cheap lookups. Leave it false for anything that changes data, sends something, spends meaningful money or accesses sensitive data. |
requires_permission | false | Hold the exact call until the caller clearly approves it. Dialt asks naturally, verifies the reply and executes nothing when approval or scope is unclear. |
resolver_only | false | This tool only receives the caller's answer to one of your own questions (an interaction resolver). The model never sees it and cannot call it; Dialt constructs the exact call when the caller answers. Cannot combine with requires_permission or read_only. |
recorded_bridge | "auto" | Controls Dialt's prerecorded latency cover only. "auto" races the complete answer against the tier's fixed response budget. "always" plays cover when the tool route commits. "never" waits silently for the result or timeout. Model-authored speech remains ordinary conversation speech. The deprecated expected_duration alias maps instant to auto and seconds/long to always; do not send both. |
status_label | Tool name | Short caller-safe label Dialt may use when the caller asks about pending work. Do not put arguments, internal identifiers or secrets here. |
timeout | 30 seconds | Result deadline; minimum 1 second, maximum 600 seconds. |
deferred | false | Allow the host to release the current voice turn while keeping this call registered as a background job. |
deferred_timeout | 7200 seconds | Job lifetime after an accepted defer, up to 24 hours and no shorter than timeout. |
notify_on_complete | true | Queue one proactive completion when the floor is free. The result enters context either way. |
mode.tool_choice restricts tool use with the familiar vocabulary: "auto" (default), "none" (client tools withheld; the broker's own managed tools such as web_search and end_call, and protocol tools like job cancellation, stay available), "required", {"allowed": [names]}, or {"tool": name}. Forced modes constrain the first planning round of each caller turn; later rounds are unconstrained so the reply can still be spoken. Change it mid-session with {"type": "set_tool_choice", "tool_choice": ..., "one_shot": true?}. It applies from the next reply, one_shot reverts after the next caller or explicitly host-requested reply consumes it, autonomous jobs do not consume it, unknown names are rejected with invalid_tool_choice, and a mid-session set_tools resets the choice to "auto".
When requires_permission is true, the first call is held inside Dialt and is not sent to your host. A clear approval releases the original name and arguments exactly once; refusal discards it; ambiguity remains pending for clarification. Changed arguments create a new permission request. Pending permission IDs are one-time, session-scoped and expire after five minutes by default. Permission questions and acknowledgements are generated naturally from the conversation.
Test caller permission without audio
The default permission_source: "caller" works in text mode: typed input_text turns pass through the same consent gate as transcribed speech. No separate consent frame or dry-run mode is required. Use a fresh text session and a fixture tool that records calls and returns a simulated result, without connecting it to a real write target.
- Declare the fixture with
requires_permission: true, setmode.modality: "text"and send the requested action as aninput_textturn. - Wait for
permission_pendingand the assistant's completed approval question. Record the pendingid, tool and arguments. Assert that notool_callwas dispatched. - Send a typed caller answer. In separate sessions, cover explicit approval, refusal, ambiguity and an answer that changes the action. After each completed reply, assert that refusal or ambiguity did not dispatch the original call and that changed arguments did not reuse its authorization.
- For clear approval of the exact action, expect one
tool_callwith the pending ID, name and arguments. Record it, return a fixturetool_result, and assert it was dispatched only once. Give each wait a test deadline; a timeout fails the test rather than proving refusal.
permission_refused reports a resolved refusal; ambiguity can leave the request pending. permission_approved is emitted after the admitted execution path returns, including accepted deferral. It is not a pre-execution receipt or proof the business operation succeeded. Do not wait for it before handling tool_call, or you can deadlock the result exchange. Correlate permission and tool events by ID, and use your host's result to establish the operation's outcome.
permission_source: "application" is a different contract: your authenticated host submits resolve_tool_permission. Use it when the application owns approval; it does not test caller-consent verification. Text tests exercise tool admission and typed consent, not transcription or acoustic interruption behavior.
Dialt calls the host:
{"type":"tool_call","id":"turn3-fc1","name":"lookup_order","args":{"order_id":"A123"}}
Complete exactly once with a result or client cancellation:
{"type":"tool_result","id":"turn3-fc1","outcome":"succeeded","verified":true,"content":{"speak":"The order shipped.","data":{"status":"shipped"},"handle":"order-A123"}}
{"type":"tool_cancel","id":"turn3-fc1"}
Report the outcome
Send outcome and verified whenever your host can establish them. The assistant words its reply from this pair:
"outcome":"succeeded","verified":truelets the assistant confirm the action: "You're booked for Tuesday." Send it when your host holds proof, for example a confirmation ID returned by your booking API."outcome":"unknown", or both fields omitted, makes the assistant say it cannot confirm the action yet. This suits queued or fire-and-forget work. Omitted fields default tounknownandfalse."outcome":"failed"makes the assistant say the action did not go through.
{"type":"tool_result","id":"turn3-fc1","outcome":"succeeded","verified":true,"content":{"confirmation_id":"BK-4821","date":"Tuesday"}}
{"type":"tool_result","id":"turn3-fc2","outcome":"unknown","content":{"status":"queued"}}
{"type":"tool_result","id":"turn3-fc3","outcome":"failed","content":{"error":"slot_unavailable"}}
verified may be true only with succeeded. A timeout stays unverified because the host cannot know whether the action happened.
For longer work, progress does not resolve the call or interrupt playback:
Or acknowledge an eligible call as deferred before its initial deadline:
{"type":"tool_deferred","id":"turn3-fc1","handle":"cc-task-123","status_label":"Claude Code task"}
An accepted tool_deferred_ack releases the voice turn and starts the longer deferred deadline. Progress, cancellation, partials and exactly one terminal result may then use the original ID or handle. On ordinary WebSocket resume, tool_deferred_resume re-announces jobs so the host can associate its local worker. Barge-in does not cancel them.
{"type":"tool_progress","id":"turn3-fc1","note":"checking the carrier"}
A running call that needs a mid-call decision raises it with a tool_partial_result carrying interaction (a stable id, a prompt, optional options); Dialt asks by voice at the next opportunity and acks the lifecycle over tool_job_narration (queued/started/superseded/cancelled/failed/resolved, with interaction_ids). The host can close an open interaction without completing the call: decision made elsewhere, no longer needed, or overtaken by newer intent:
{"type":"tool_interaction_update","id":"turn3-fc1","interaction_id":"overwrite-1","state":"resolved","note":"approved in the IDE"}
Pending or in-flight narration for that ask stops, the agent is told not to act on it, and every update receives a deterministic tool_interaction_update_ack (applied: false carries a stable reason such as already_closed for late or duplicate updates). A caller interrupting the ask's narration does not close the decision; it stays pending and answerable until the host closes it or the call ends. See the AsyncAPI contract for exact payloads.
- Result
contentis limited to 16 KiB of compact UTF-8 JSON by default. Oversized results still resolve, but Dialt replaces them with bounded content containingtool_result_truncated,original_bytesand a preview. - Progress notes are limited to 500 characters and 12 notes per call.
- A server
tool_cancelasks the host to stop promptly because the result is no longer usable or the caller explicitly asked Dialt to cancel the pending job. - Barge-in never cancels tool work. It stops the spoken reply while the tool continues. Dialt exposes the pending job to its model using the declared safe status label; a semantic, explicit request to stop is handled by Dialt's managed cancellation tool. Hosts should not declare a duplicate generic stop tool.
- Built-in web search always acknowledges first with
recorded_bridge: "always". - Keep verbose logs and artifacts in your own system. Return a short spoken summary, structured data and, when useful, a handle or URL.
Errors, reconnects and limits
Spot capacity
Spot is an API-only capacity/pricing option. Request mode.capacity: "spot" with Fast reasoning and voice modality. Capacity defaults to "standard"; brain remains "fast" | "smart". Spot does not support Smart, text, playground or hosted evals. Eligible stock keys are us_female, us_male, british_male, british_female, mexican_female and brazilian_female. See pricing and availability terms.
Admission atomically reserves a session only if the total in-flight count after reservation is at most 32. This cutoff counts all tiers. Existing calls are never evicted or repriced when admission closes. mode.spot_fallback accepts "none" (default) or an explicit "standard", and is valid only with requested Spot. Standard-price fallback keeps Fast and the same voice and applies only when Spot admission is closed; an unsupported voice never triggers fallback.
Every Spot-originated ready includes admission with requested_capacity: "spot", admitted_capacity: "spot" | "standard", rate_micro_per_hour and fallback. Spot terms are 1990000 micro-dollars/hour with fallback: false; admitted standard terms are the normal Fast rate of 3900000 with fallback: true. These terms are pinned for the conversation. Resume restores them without a new fallback decision.
spot_unavailable and spot_voice_unavailable have retryable: false and end automatic reconnect. spot_unavailable may carry retry_after_seconds: 5 as advice for a deliberate new attempt. SDKs do not automatically retry these errors or silently start a new conversation.
invalid_voice is nonretryable: choose a current stock voice key from GET /v1/voices. Empty, unknown, retired and unavailable keys never fall back. Startup closes with code 1008; an invalid set_voice leaves the active voice unchanged. Session voice selection is optional: an explicit voice takes precedence over a saved preference, then US Female (us_female) is the shared default. The voice route is independent of Dialt Fast or Dialt Smart.
The catalogue's default and aliases fields identify the effective selection. Its voices reflect the deployment's configured listening capability, not live provider health. Show unavailable saved choices and ask for a new selection; do not silently replace them. In voice sessions, set_voice and agent continuations must preserve the initial listening route and language hints. Start a new call to change these. Text sessions may change language. Every accepted set_voice, including the active key, receives a voice confirmation.
Voice catalogue and migration
Anonymous voice lists contain shared voices only. Authenticated lists also include Dialt-managed voices available to the credential's Owner or selected browser workspace. Use that same Owner's credentials to select a restricted voice. Guessing an unavailable key returns invalid_voice; team status does not grant access. Resume rechecks current access, including when a saved identity was previously available. Provider configuration and access allowlists remain internal.
Owner-restricted stock voices use mode.voice with ordinary instructions. They are unavailable as Voice Design acoustic-prompt bases. They require standard capacity: a Spot request rejects them even with spot_fallback: "standard". Select only keys returned for the session's Owner.
The regional choices are US Female (us_female), US Male (us_male), British Male (british_male), British Female (british_female), Mexican Spanish Female (mexican_female) and Brazilian Portuguese Female (brazilian_female). British Female and Mexican Spanish Female retain their keys with new speaking identities. Public regional names describe region and gender.
southern_us_female remains accepted as an alias of us_female: the voice changes from Southern US to general US English, with a new speaking identity. brazilian_male remains accepted as an alias of brazilian_female: the voice changes from male to female, including feminine self-reference. These aliases are absent as duplicate picker options. Use the canonical keys in new integrations and review these accent, gender and identity changes before restoring saved selections.
Arabic Female (arabic_female) is the shared Arabic choice. Egyptian Arabic Female (egyptian_female), Egyptian Arabic Male (egyptian_male) and Egyptian Male Beta (egyptian_beta_male) are available only in the Olimi and Dialt organization workspaces. Members' personal workspaces are excluded. These keys retain their identities; they are not aliases of Arabic Female. Previously shared saved identities cannot bypass current access on resume.
Saudi Arabic Female (saudi_female) is retired in every workspace. Explicit selection and resume return invalid_voice, including previously saved identities. There is no replacement alias. Select arabic_female for Modern Standard Arabic or an authorized Egyptian voice for Egyptian Arabic; neither promises Saudi dialect.
Irish Male (classic), Chime (chime), Servo (servo) and Circuit (circuit) retain their identities. A resumed session retains its confirmed stock voice identity when current access permits it, unless explicitly changed.
Customer voice conditioning and voice creation/deletion are removed. Remove clip-upload and voice-management calls, and replace saved conditioned-voice keys and preferences with a current stock key from GET /v1/voices. Stock listing, session voice selection and mid-session voice changes remain supported.
Hinglish Female (hinglish_female) restores the previously retired key with a new shared speaking identity. It requires standard capacity; Spot rejects it even with spot_fallback: "standard". Hinglish Male (hinglish_male) is unchanged and available only on deployments whose voice list includes it. Both use Hindi-English wording in Roman script by default. Fetch the current voice list before selecting or restoring a saved voice.
missing_instructions is nonretryable: a fresh start must carry non-empty mode.instructions (see Assistant instructions). Startup closes with code 1008. invalid_instructions rejects an empty set_instructions and keeps the current instructions.
A speech synthesis failure during a turn sends retryable pipeline_unavailable followed by that turn's done frame. The connection and input pipeline stay open, so the application may continue the call and the caller may retry. Dialt does not commit an assistant answer that produced no audible output.
detail | What to do |
|---|---|
unauthorized | Replace a missing, unknown, expired or revoked credential. |
invalid session_id | Use a safe non-empty identifier and match the scoped credential. |
insufficient credit | Add credit in API & Billing. |
too many concurrent sessions | Close the existing session or wait for it to end. |
server busy, try again shortly | Retry with exponential backoff. |
server restarting, try again shortly | Retry shortly. |
voice pipeline unavailable, try again shortly | Retry with backoff and surface a temporary-service message. |
- A per-account limit on concurrent API sessions, two by default; check
session_limitonGET /billingfor your account's configured and effective values. Hosted evals are admitted from a separate per-account allowance, so an eval run never competes with your API sessions. - Each connection is capped at two hours.
- Voice is $0.065 per connected minute on the default
fastbrain, $0.085 on thesmartbrain, metered per second with partial seconds rounded up. - Text uses the same meter at a 90% discount: committed user and assistant words are converted at 150 words per minute; prompts, tool payloads and eval judges are excluded.
- An abnormal browser transport loss is retried automatically; a successful resume restores the existing conversation and keeps its server session UUID. If resume fails, the SDK reports
resume_failedso the application can offer a fresh start. - A clean server close ends the session and is not automatically reconnected.
To validate a key without opening a billable session, connect and send {"type":"auth","api_key":"dk_…"}. A valid key receives {"type":"ok"} and the socket closes.
WebRTC transport signaling (experimental)
Experimental: the API is stable, but this transport is newly shipped and still being hardened on real networks; ws remains the default and recommended fallback.
The same /v1/realtime connection can also negotiate a WebRTC (SRTP over UDP) media path instead of carrying audio directly. Signaling is two steps, because the API server mints credentials for the separate turn.dialt.com service: a client that gathers ICE before it has those credentials can never use TURN.
- Open
/v1/realtimeas usual and send the start frame with"transport": {"kind": "webrtc"}and no SDP yet. Requiresmode.kinddialt. - The server replies
webrtc_icewith STUN/TURNice_serversandtrickle_ice: truewhen it accepts later candidates. - Only now build the
RTCPeerConnectionwith those servers and sendwebrtc_offerimmediately. When trickle ICE is advertised, follow it with eachwebrtc_candidateand thenwebrtc_ice_complete. Without that flag, send the fully gathered offer for compatibility. - The server answers
webrtc_answerwith its SDP. Keep signaling open while trickling candidates. The call then lives on the peer connection; the signaling socket may drop afterward without affecting it.
// server -> client
{"type":"webrtc_ice","ice_servers":[{"urls":["stun:stun.l.google.com:19302"]}],"trickle_ice":true}
// client -> server
{"type":"webrtc_offer","sdp":"v=0..."}
{"type":"webrtc_candidate","candidate":{"candidate":"candidate:...","sdpMid":"0","sdpMLineIndex":0}}
{"type":"webrtc_ice_complete"}
// server -> client
{"type":"webrtc_answer","sdp":"v=0...","ice_servers":[...]}
Once connected, control frames ride a "control" RTCDataChannel, byte-identical to the JSON frames documented above: the same type values, the same fields. Microphone and assistant audio ride RTP media tracks instead of binary WebSocket frames.
Before closing, the server sends {"type":"bye","code":...,"reason":"..."} on the control channel, the WebRTC counterpart of a WebSocket close code.
Clients on this transport must not send client_event/playback_stopped: the server can see what it queued but never sent on the RTP track, so it synthesizes that report itself on a hard-clear interruption.
Framework conversations
response_done carries response_id and terminal status: completed, cancelled or failed, once at logical response teardown. It applies to responses with or without a host reply operation. done remains a per-segment output fence identified by turn_id; a bridge segment ending does not end the logical response. response_done may precede the final done. Settle logical generation independently from segment output and preserve queued speaker audio. Neither event proves playback or that the caller heard the response.
On a fresh connection, send initial_history at the top level of start. Python accepts DialtSession.connect(mode=mode, initial_history=items). Loading history starts no reply. For explicit startup, disable the automatic greeting with greeting=False, wait for readiness, then call await session.request_reply(operation_id="startup-1").
Every history item has a unique item_id, positive revision and kind. Messages have role user or assistant and nonempty text; assistant messages may include interrupted. Completed tools use tool_call with call_id, name and JSON-object arguments, followed by exactly one tool_result with the same call ID, JSON content and terminal outcome: succeeded, failed, cancelled, timed_out or unknown. Importing tools never executes them. Initialization rejects the entire history for pending calls, unknown fields, duplicate IDs or limits above 256 items, 128 KiB compact UTF-8 JSON overall or 16 KiB per item.
Keep system/developer messages in your developer instructions. Public history excludes internal Dialt prompts, reasoning, media and pending work. It includes completed tool evidence, so treat its contents as sensitive application data. There are no history edit or delete operations. Resume omits initial history and retains accepted state; reset starts empty.
conversation_snapshot after ready replaces the accepted read-only window. conversation_item updates use absolute index and first_index: higher revisions replace the same item in place, while duplicates and older revisions are ignored. Server-owned empty text revisions retract unheard speech. The window retains at most 512 items and 512 KiB; first_index makes eviction explicit. Evidence above 64 KiB sets content_omitted=true. Python session.conversation returns detached copies. Streaming text is not yet accepted history.
request_reply uses an operation ID and optional response-scoped instructions. Its receipt reports accepted and status queued, started, completed, cancelled, failed or rejected. Later receipts remain visible through events. The queue holds 16 requests and the session retains 256 reply receipts. Retry an uncertain outcome with the same ID and payload to retrieve its latest receipt, including after resume. Never blindly replay with a new ID.
await session.interrupt(response_id=...) stops only that logical response; await session.interrupt(target_operation_id=...) cancels only a queued reply. With neither target Python selects the current active response, and fails if none exists. Responses keep one opaque ID across segments; turn_id remains a segment compatibility field. Delayed stops for old responses cannot stop newer output or cancel dispatched tools. Report discarded speaker audio using await session.playback_stopped(response_id, discarded_ms), even after generation completed. Legacy client-event reporting remains available.
Ready advertises conversation_v1, request_reply_v1, targeted_interrupt_v1 and speech_activity_v1. New SDK operations fail explicitly when their capability is absent. Python requests conversation_v1 while preserving your other client capabilities. WebRTC lacks targeted interruption: the server owns track flushing and discard accounting, and clients must not attribute RTP samples or send duplicate discard reports.
Voice activity events speech_started and speech_stopped identify detected episodes with speech_id and monotonic session-relative timestamp_ms. They are separate from semantic turns, transcript availability and interruption decisions. Text sessions have no activity events. Disconnect clears the local activity indicator; resumed ready refreshes capabilities and active_speech and reaches event observers. Activity alone must not trigger a stop.
Platform support
| Platform | Status | What you provide |
|---|---|---|
| Desktop Chrome / Edge | Production validated | Browser SDK and scoped credential route. |
| Desktop Firefox | API compatible; live validation pending | Browser SDK and native browser echo cancellation. |
| Safari / iOS WebKit | API compatible; not yet production validated | WebSocket transport and SDK AEC3. See the browser platform guide for device-validation limits. |
| Embedded WKWebView | Host-app validation required | Safari compatibility does not certify an embedded app. |
| Python service / telephony / custom hardware | Supported headless transport | Capture, pacing, playback and echo cancellation. |
| Raw WebSocket | Supported advanced integration | The complete media and session lifecycle. |
| Native macOS 14+ / iOS 17+ | Swift SDK, experimental alpha; see validation limits. | Swift 6 app, scoped credential and microphone permission. Capture, playback and native echo cancellation are included. |