Skip to main content
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 echoes finesse.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 text you send counts against budget_chars. The frame that would go over it is refused with a fatal error frame, and the socket closes with 1008 (reason insufficient_credits):
    Create a new session, with a larger budget_chars if you need one.
  • Length. A session ends after ttl_seconds at 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_by if it never connects. Direct sessions and Realtime WebSocket connections share the same slots. When every slot is in use, creating a session returns 429 with code concurrency_limited and a Retry-After header. Close a session (session_end) to free its slot.

Billing

Characters are billed like every other synthesis: per character of text 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.

Errors

Close codes

Prompt

Copy this into your coding agent (Claude Code, Cursor, Codex…) to implement this endpoint.