Open an in-browser voice session - the published version, or the draft when none is published (no phone call)
const url = 'https://api.vocapable.com/v1/agents/example/voice-session';const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}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.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Responses
Section titled “Responses”Successful Response
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
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
Example
{ "protocol": "browser.v1", "audio": { "encoding": "pcm16" }, "runs_draft": false}Validation Error
object
object
object
Examplegenerated
{ "detail": [ { "loc": [ "example" ], "msg": "example", "type": "example", "input": "example", "ctx": {} } ]}