Calls & artifacts
A call is one conversation and everything it left behind: a transcript, a recording, a timeline of typed events, per-stage latency and a per-component cost breakdown.
API
Full parameters and responses: call reference.
GET /api/v1/calls
POST /api/v1/calls
GET /api/v1/calls/search
GET /api/v1/calls/{id}
DELETE /api/v1/calls/{id}
POST /api/v1/calls/{id}/end
GET /api/v1/calls/{id}/transcript
GET /api/v1/calls/{id}/messages
GET /api/v1/calls/{id}/events
GET /api/v1/calls/{id}/metrics
GET /api/v1/calls/{id}/costs
GET /api/v1/calls/{id}/artifacts
GET /api/v1/calls/{id}/debug
GET /api/v1/calls/{id}/recording
DELETE /api/v1/calls/{id}/recording
GET /api/v1/calls/{id}/artifacts/{artifact}/download (signed URL, no API key)GET /calls/search is the same handler as GET /calls
The two routes point at one controller method. Use whichever reads better; they behave identically.
The state machine
created → queued → ringing → connecting → connected
│
┌────────────────────────────────────┴──────────────────────────┐
│ listening ⇄ user_speaking ⇄ thinking ⇄ tool_executing │
│ ⇄ assistant_speaking ⇄ on_hold ⇄ transferring │
│ ⇄ human_queue ⇄ human_connected │
└───────────────────────────────────────────────────────────────┘
│
ending → ended | failedEvery state inside the loop can follow every other, because the caller can interrupt at any instant — that is barge-in. ending, failed and ended are reachable from anywhere non-terminal. The table is defined once in packages/contracts/src/call-state.ts and mirrored in App\Enums\CallState, with a test that reads the PHP source to prove they agree. The 17 states are listed on Call states & events.
End reasons are the exception: the contracts have 19 and the control plane has 20, with different spellings, and no test holds them together. The API returns the control plane's twenty — they are listed on When something fails.
Events
Every call emits a typed event stream — lifecycle, speech, transcripts, model, TTS, tools, MCP, knowledge, routing, contact centre, DTMF, artifacts, provider health. The vocabulary is CallEventType in @callagent/contracts, mirrored in App\Enums\CallEventType. call_events is insert-only. All 70 event types and their payloads: event types.
Artifacts
| Artifact | Produced when |
|---|---|
| Transcript | Always, incrementally during the call |
| Recording | artifact_plan.recording_enabled. An announcement can be played; a refusal cannot be honoured — see AI disclosure & consent. |
| Summary | analysis_plan after the call — not dispatched, see below |
| Structured output | An assistant with a structured output attached — not dispatched, see below |
| Debug bundle | On demand, from GET /calls/{id}/debug |
Recordings can be written to your own bucket — S3, R2, Azure Blob or any S3-compatible endpoint — under an organization-prefixed path, and are deletable per call for a GDPR erasure request. See Recording & storage.
Summary and structured output
The Summary and Structured output rows above depend on the post-call analysis pipeline, which the control plane does not currently dispatch. See Analytics.
Latency and cost
GET /calls/{id}/metrics breaks the turn down: first STT partial, endpoint detected, model time-to-first-token, TTS time-to-first-audio, and the end-to-end endpoint-to-audio figure that is what the caller actually experiences.
GET /calls/{id}/costs breaks the money down per component with the provider's own units, at decimal(20,8) — an LLM token costs less than a millionth of a dollar and rounding it early makes the ledger wrong.
Live calls
The dashboard's Live Calls view reads the gateway's session snapshots — state, current tool, last transcript, per-component latency, active providers.
Supervisors can listen, whisper or barge in from there. The permission gate and the audit trail are proven; that audio actually bridges is not, and the newer gateway-media path is off by default. See Contact centre.