Speech
Create a direct realtime session
Stream speech straight from the synthesis GPU, without a relay in between.
POST
Create a direct realtime session
Creates a realtime session that your client runs directly against the synthesis GPU in the
region you choose. Audio doesn’t pass through the Finesse API, so each sentence starts sooner than
on the Realtime WebSocket.
1
Create the session on your server
Call this endpoint with your API key and the voices the session may use. The response has a
WebSocket
url, a protocol and a single-use token.2
Open the socket from your client
new WebSocket(url, [protocol, token]) before connect_by. The token travels in the subprotocol
list, so this works from a browser and your API key never leaves your server.3
Speak the same protocol
Send
task_start / task_continue / task_cancel / session_end and receive task_started,
audio_chunk + binary frames, sentence_done and task_done, exactly as described on the
Realtime WebSocket page.How it differs from the Realtime WebSocket
Use a direct session when per-sentence latency matters most, for example a voice agent with
callers far from India: create it in
us-east for callers in the Americas.
Authorization
string
required
Your workspace API key (
flv_…). Required scope: tts. Your plan must include streaming.Body
string[]
required
The voices this session may use: 1 to 16 of your voices or public platform voices, each enrolled.
Duplicates are ignored. A
task_start for any other voice gets a non-fatal voice_not_found
error frame; the rest of the session continues.string
The GPU region to run in:
ap-south (India) or us-east (US East). Omit it for the default
region. Any other value returns 400.integer
default:20000
Characters to reserve for this session,
1–200000. They’re taken from your balance now, and
whatever the session doesn’t use is returned when it ends. If your balance can’t cover the
budget, a budget of 1,000 is reserved instead and returned in data.budget_chars.Response
201 Created. The response carries Cache-Control: no-store.
string
The session’s id.
string
The WebSocket URL of the GPU region, e.g.
wss://…ap-south.modal.direct/v1/stream. Connect to this URL as returned; don’t build it yourself.string
Always
finesse.v1. Offer it as the first subprotocol.string
The session token (
ft1.…). Offer it as the second subprotocol. It works for one connection,
opened before connect_by, and is shown only in this response.string
RFC 3339 time by which you must connect, 60 seconds after creation.
integer
The longest the session can run once connected (
3600).integer
The characters actually reserved. The session can’t synthesize more than this.
string
The region the session runs in.
string[]
The voice ids the session may use.
Response
Connecting
The server selects and echoesfinesse.v1. The token is checked once the socket is open: a token
that is invalid, changed, already used, or used after connect_by closes the socket with code
4401 (reason unauthorized). Create a new session to try again; a token can never be reused,
including to reconnect after a dropped connection.
Limits
-
Budget. Every character of
textyou send counts againstbudget_chars. The frame that would go over it is refused with a fatal error frame, and the socket closes with1008(reasoninsufficient_credits):Create a new session, with a largerbudget_charsif you need one. -
Length. A session ends after
ttl_secondsat most, however much budget is left. -
Concurrency. Each session holds one of your workspace’s
concurrent stream slots from creation until it ends, or until shortly after
connect_byif it never connects. Direct sessions and Realtime WebSocket connections share the same slots. When every slot is in use, creating a session returns429with codeconcurrency_limitedand aRetry-Afterheader. Close a session (session_end) to free its slot.
Billing
Characters are billed like every other synthesis: per character oftext in task_start and
task_continue frames. The whole budget_chars is reserved from your balance when the session is
created, so your balance dips by that much while the session is open. When the session ends, you’re
charged for the characters it used and the rest is returned. A session that is never connected is
returned in full shortly after connect_by.
If your balance can’t cover even 1,000 characters, creating a session returns 402.

