Engine protocol
Engine protocol
The contract between the platform and a voice engine, for people who run or build one.
A voice engine is a separate service. The platform starts a call, hands the engine a session, and the engine reports back events. This page describes that contract as the platform's own engine implements it. You need it only if you operate the engine or want to write your own.
The flow of an outbound call
POST /callcreates the execution. The worker admits it against the workspace's concurrency limit.- The platform stores the call session and dials through the telephony provider, giving it an answer URL and a status URL, each carrying a per-call token.
- When the callee answers, the provider fetches the answer URL. The platform replies: stream this call's audio to the engine's WebSocket. The stream carries the
execution_idand the token. - The provider posts progress (ringing, in progress, completed with a duration, busy, no answer, failed) to the status URL. The platform maps it to execution statuses and webhooks.
- The engine reports the conversation. After the provider says the call is over, the platform waits a short time for the engine's final
result, then runs the post-call work and marks the executioncompleted.
For an inbound call, the linked number's webhook answers with the same stream instruction, and the rest is identical. Stopping a call hangs up through the provider; the execution ends stopped.
Authentication
Every request from the engine to the platform carries Authorization: Bearer <token>, where the token is the one delivered with the stream. A few engine-level calls (registering an inbound call, redeeming a browser test ticket, reporting a refused call) use the shared secret instead.
Get the session
GET /engine/sessions/{execution_id}{
"execution_id": "0b0f4e5c-7c0a-4d4e-bb2b-6b3b8a1f2c9d",
"agent_id": "123e4567-e89b-12d3-a456-426655440000",
"agent_config": {},
"agent_prompts": {"task_1": {"system_prompt": "..."}},
"api_tools": {},
"user_data": {"customer_name": "Asha"},
"context_data": {"recipient_data": {}},
"call_type": "outbound",
"to_number": "+919876500000",
"from_number": "+918000000000",
"events_url": "https://api.sevennadi.si/engine/events/0b0f4e5c-7c0a-4d4e-bb2b-6b3b8a1f2c9d"
}Prompts arrive with their {variables} already filled. agent_config is the agent exactly as saved, so every option set on it reaches the engine. The session also carries the redaction policy (pii_redaction: off, mask or strict), the AI disclosure line if one applies, and has_knowledge_base.
Report events
POST /engine/events/{execution_id}The body is one event or an array of them.
kind | Fields | Meaning |
|---|---|---|
turn | role (assistant or user), text | One finished turn; builds the live transcript |
usage | component, provider, model, units, unit, cost | What a model stage consumed |
log | type, component, data | A line for the call's raw log |
tool_call | name, arguments, result, status | A function tool ran |
transfer | to_number | The engine is handing the caller to a person |
status | status | A status the provider cannot report itself, for example in-progress on a directly dialled stream |
result | see below | Once, at the end |
The final result:
{
"kind": "result",
"messages": [{"role": "assistant", "content": "Hello Asha..."}],
"ended_by_assistant": true,
"transcriber_duration": 20,
"synthesizer_characters": 300,
"llm_tokens": 900,
"hangup_detail": {"reason": "caller hung up", "code": "caller_hangup"},
"node_extracted_data": {"order_id": "ORD-1"},
"node_path": ["welcome", "collect_order"]
}- If no
turnevents were sent,messagesbecomes the transcript. - Usage given as totals in the result is priced by the platform from the price book. A
usageevent that carries its owncostis believed instead. - Graph agents report
node_extracted_dataandnode_path. A transferred call reportstransfer_call_data. A detected answering machine reportsanswered_by_voice_mail. Stretches recorded as silence on purpose are listed inrecording_gaps. - The platform masks personal data in incoming events again, under the session's policy.
The engine posts a result however the call ends, including a dropped socket. Its hangup_detail.code is one of caller_hangup, stream_closed, inactivity_timeout, max_duration, end_call, stop_requested, transfer, voicemail, graph_end, backend_failure, session_unavailable, engine_error or engine_shutdown.
Knowledge base
Only when the session says has_knowledge_base:
POST /engine/sessions/{execution_id}/knowledge{"query": "what the caller said", "k": 3}The answer is {"passages": ["...", "..."]}, best first. It is empty when nothing is relevant and also when retrieval fails; it never answers 500. Treat it as optional context and give it a short timeout.
Recording
An engine that records the call uploads the WAV file before posting the result, and puts the returned URL in the result as recording_url:
PUT /engine/recordings/{execution_id}The file may be up to 60 MB. The URL becomes the execution's recording unless the telephony provider reported one of its own.
Stop requests
GET /engine/sessions/{execution_id}/controlPoll every second or two. When the answer turns to {"stop": true}, close the stream and post the result. Twilio and Plivo calls are also hung up by the platform; an Exotel call ends only this way.
Transfer
When a transfer_call tool runs, the engine reports a transfer event and ends its stream. Twilio and Plivo calls are redirected to the person by the platform. On Exotel, closing the stream lets the next step of the call flow connect them.
Sending an SMS from a call
The engine does not send messages itself. For a send_sms tool it asks the platform, which takes the text from the agent's tool settings and applies the compliance checks:
POST /engine/sessions/{execution_id}/tools/send_smsBrowser test calls
POST /agent-voice-test/{agent_id} gives the dashboard a one-time ticket and the engine's WebSocket address. The engine redeems the ticket with the platform and receives the same session object as for a phone call, marked as a web call. Events, recordings, knowledge and stop requests then work as for any call. The call record is an ordinary execution with a telephony cost of zero. See Browser voice test.
Capacity
An engine that refuses a call because it is full reports it to the platform, so the refusal is visible to the operator. The engine also exposes its capacity (active_calls, max_calls, whether it is draining) for the platform's readiness check.
Writing your own engine
The minimum is small: accept the stream, read execution_id and the token from it, fetch the session, run your pipeline, post turn events as each side finishes speaking, poll the control endpoint, and post one result on disconnect. Everything else on this page is optional.