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

  1. POST /call creates the execution. The worker admits it against the workspace's concurrency limit.
  2. 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.
  3. 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_id and the token.
  4. 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.
  5. 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 execution completed.

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

http
GET /engine/sessions/{execution_id}
json
{
  "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

http
POST /engine/events/{execution_id}

The body is one event or an array of them.

kindFieldsMeaning
turnrole (assistant or user), textOne finished turn; builds the live transcript
usagecomponent, provider, model, units, unit, costWhat a model stage consumed
logtype, component, dataA line for the call's raw log
tool_callname, arguments, result, statusA function tool ran
transferto_numberThe engine is handing the caller to a person
statusstatusA status the provider cannot report itself, for example in-progress on a directly dialled stream
resultsee belowOnce, at the end

The final result:

json
{
  "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 turn events were sent, messages becomes the transcript.
  • Usage given as totals in the result is priced by the platform from the price book. A usage event that carries its own cost is believed instead.
  • Graph agents report node_extracted_data and node_path. A transferred call reports transfer_call_data. A detected answering machine reports answered_by_voice_mail. Stretches recorded as silence on purpose are listed in recording_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:

http
POST /engine/sessions/{execution_id}/knowledge
json
{"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:

http
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

http
GET /engine/sessions/{execution_id}/control

Poll 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:

http
POST /engine/sessions/{execution_id}/tools/send_sms

Browser 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.