Skip to content

Open an in-browser voice session - the published version, or the draft when none is published (no phone call)

POST
/v1/agents/{agent_id}/voice-session
curl --request POST \
--url https://api.vocapable.com/v1/agents/example/voice-session \
--header 'Authorization: Bearer <token>'

Mint one browser session against the agent’s latest published version.

Everything that can refuse this session refuses it here, in HTTP, with a body. The alternative is what the runtime would otherwise do: accept the socket, redeem the token, fail a floor check, and close with a WSS 1008 and no payload - the worst diagnostic in the product, because the operator learns only that “it didn’t work”. So the same four checks the redemption runs (modules/internal/bootstrap_checks.py, imported rather than reimplemented) run against the same pinned version before a token is minted at all: voice_profile_missing and disclosure_opener_missing as 409s the tenant can fix, llm_chain_unresolvable and runtime_plane_unavailable as 503s only a deployment can.

Nothing is written. A browser session’s durable state begins at teardown, which is where its calls row and its compliance evidence land (migration 0031) - so a session the operator never connects leaves no attempt, no call and no meter behind it.

The admission gates, and why they run where they do

The budget gates (.admission) run first, before any read: a caller who has spent their address’s hourly budget or their account’s daily one must not cost us four queries to find that out, and both refusals are the same whatever agent was named.

The lane reservation runs last, immediately before the mint. It is the one gate that holds something, so it is taken only once every reason to refuse this session has been ruled out - a reservation abandoned behind a voice_profile_missing would block that account’s next attempt for the reservation’s whole life. If minting fails after it is taken, it is given back in the same request.

agent_id
required
Agent Id
string

Successful Response

Media typeapplication/json
VoiceSession

POST /v1/agents/{id}/voice-session - one in-browser voice session, as the 201 answers it (docs/05-voice-runtime.md §Browser transport, CANON fact 103).

Nothing here names a called party, because there is none: the session is a transport swap onto the same runtime, so it carries the same pinned AgentVersion, the same compliance floor, and the same single-use session token the carrier leg redeems - and no number, no contact, no attempt and no telephony account.

session_token is a single-use bearer credential: it is redeemed once at POST /internal/call-sessions/redeem and is worthless afterwards. It is returned in the body and never persisted by the API, which is why this endpoint accepts no Idempotency-Key (see the router’s docstring).

disclosure_opener is the exact first utterance the session will open with. Returned before the microphone is ever enabled so the portal can show the operator what the agent is about to say - the AI-disclosure floor as visible product surface rather than an invisible control (CANON fact 71).

object
call_id
required
Call Id
string
agent_id
required
Agent Id
string
agent_version_id
required
Agent Version Id
string
ws_url
required
Ws Url
string
gpu_node_id
Any of:
string
session_token
required
Session Token
string
expires_at
required
Expires At
string format: date-time
protocol
Protocol
string
default: browser.v1
Allowed value: browser.v1
audio
required
VoiceSessionAudio

The wire audio format both directions of a browser voice session carry.

Load-bearing rather than descriptive: BrowserTransportSink.send_audio RAISES on anything but 8 kHz mono PCM16 (apps/voice-runtime/.../browser_transport.py), and the browser is the side that resamples - there is no server-side resampler to rescue a client that guessed 48 kHz. Returned in the 201 so the client reads the contract it must satisfy instead of inferring it from a socket that closes mid-call.

object
encoding
Encoding
string
default: pcm16
Allowed value: pcm16
sample_rate_hz
required
Sample Rate Hz
integer
channels
required
Channels
integer
max_call_seconds
required
Max Call Seconds
integer
disclosure_opener
required
Disclosure Opener
string
runs_draft
Runs Draft
boolean
created_at
required
Created At
string format: date-time
Example
{
"protocol": "browser.v1",
"audio": {
"encoding": "pcm16"
},
"runs_draft": false
}

Validation Error

Media typeapplication/json
HTTPValidationError
object
detail
Detail
Array<object>
ValidationError
object
loc
required
Location
Array
msg
required
Message
string
type
required
Error Type
string
input
Input
ctx
Context
object
Examplegenerated
{
"detail": [
{
"loc": [
"example"
],
"msg": "example",
"type": "example",
"input": "example",
"ctx": {}
}
]
}