Tools and the events channel
A Tool is a function the model may call during a Call. An http Tool is a signed request Vocal sends to your endpoint; a function Tool is handled by your server over the Call's events channel, a WebSocket that also carries the Call's events as they happen and takes control commands.
On the Phase 0 host. Served: Tools, the events channel and the control route since contracts 1.49.0, the
transfercommand since 1.50.0. The routes for Tools from integrations (mcp) are served since 1.60.0, and the integrations service that runs them is not deployed; see Integrations.
Create a Tool and attach it
POST /v1/tools creates a Tool with its name, its description for the model, its parameters as a JSON Schema and its type. An http Tool also names its request, with an auth header that is write-only. Attach Tools to an Agent with tool_ids on the draft; publishing freezes a copy of each into the Agent version, and every model of the Agent must declare tool calling. type: "mcp" is refused by this route.
POST /v1/tools/{tool_id}/test sends one real, signed request of an http Tool and answers its status and timings; a function Tool needs a live Call and cannot be tested this way.
Verify an http Tool request
Every request goes out through the egress guard, which refuses private and internal addresses, and carries these headers:
| Header | Value |
|---|---|
Vocal-Signature | t=<unix seconds>,v1=<hex>: an HMAC-SHA256 over the timestamp, a period and the raw body, keyed by the UTF-8 bytes of the Organization's tool signing secret |
Vocal-Call-Id | The Call the request belongs to |
Vocal-Tool-Call-Id | The one tool call |
Idempotency-Key | For a Tool marked idempotent, derived from the two ids |
The api mints the signing secret when an Organization first needs it and never shows that one. POST /v1/tool-signing-secret/rotate mints a new one and answers it once, so rotate once to obtain a secret. The previous secret keeps signing for 86,400 seconds by default, and a request in that window carries two v1 values, the new one first; accept either.
Open the events channel
GET /v1/calls/{call_id}/stream upgrades to a WebSocket. Each server message is a StreamServerMessage with a seq; each client message is a StreamClientMessage.
include=transcriptsadds the transcript messages and needstranscripts:read.include=tool_callsmakes the connection afunctionTool handler: it receivestool.invokedwith the arguments and answers withtool.result. While no handler is connected, afunctionTool returnsno_handler.- Naming either makes opening the connection an audited content read.
- Reconnect with
after_seqto resume within the retained window, the last 1,000 messages kept until 10 minutes after the Call ends (targets); past it, read the Call's events. - Up to three connections per Call (a target); a fourth answers 429
stream_limit_reached.
Send a control command
POST /v1/calls/{call_id}/control sends one command to the live Call: say, transfer, end or inject_context, the same commands the events channel takes. It answers 202 with the command id once the Call acknowledges it, within 2 seconds (a target), or 503 session_unreachable; a Call that is not live answers 409 call_not_active. A command passes through the same Policies as model output, and its outcome arrives as command.completed on the channel. transfer is a cold transfer to a destination the Agent version names and the Organization's transfer allowlist holds; any other is refused with 403 transfer_refused.