How a call runs
Everything between your request and the finished record. Worth reading once end to end, because most of the questions that start "why did it…" are answered by knowing which of these stages the call was in.
The stages
POST /v1/calls
│
├─ 1. accepted 202, status "queued", you have a call_id
├─ 2. checked suppression list, consent, calling hours
├─ 3. admitted concurrency ceiling and project budget
├─ 4. dialled the carrier places the call
├─ 5. answered somebody, or something, picks up
│
├─ 6. greeting the agent speaks first — interruptible
│ ╭─────────────────────────────────────────────╮
├─ 7. │ LISTENING ──▶ THINKING ──▶ SPEAKING │ one turn
│ │ ▲ │ │
│ │ ╰────────── barge-in ──────╯ │
│ ╰─────────────────────────────────────────────╯
│
├─ 8. ended somebody hung up, or the agent did
└─ 9. analysed summary and extractions, seconds later1–3 · Before anything is dialled
POST /v1/calls answers 202 Accepted with a call id and
status: "queued". It does not wait for the phone to ring — a request that blocked
until a carrier answered would block for thirty seconds and time out in most HTTP clients.
Three checks run before a carrier is touched, and each has its own refusal so you can tell them apart:
| Check | Refused with |
|---|---|
| The number is on your suppression list, consent is required and missing, or it is outside calling hours where the number actually is | A compliance refusal naming which of the three. See compliance. |
| Your account is already at its concurrent-call ceiling | The call ends with
end_reason: at_capacity. See concurrency. |
| The project this agent bills to is over its monthly budget | The call ends with
end_reason: over_budget. See projects. |
4–5 · Dialling and answer
The carrier places the call and reports back. This is where most calls that never become
conversations end, and end_reason distinguishes seven ways that happens —
no_answer, busy, rejected, bad_number,
voicemail, carrier_failure and not_connected. The last
is the subtle one: the number was dialled and the far end's telephone never started ringing at
all, which is a different problem from ringing out, and worth a different response in your
code. Transfers and endings has the full vocabulary.
Machine detection runs alongside the call rather than in front of it. Waiting
for a verdict before connecting audio adds seconds of silence to the opening of every call,
including the ones a person answered — so the agent starts talking and the verdict arrives
during the greeting. What happens on a voicemail verdict is
conversation.voicemail: continue, hang up, or leave a message. See
conversation behaviour.
6 · The greeting
The agent speaks first, and the greeting is a turn like any other — which is the part worth knowing, because it means it can be interrupted. A caller who keys ahead of a menu option, or starts talking over the opening line, stops it and is heard.
Two details you can rely on:
- The recognizer opens behind the greeting, not in front of it. Opening a speech stream is a handshake with a vendor and can take most of a second; nothing in the greeting needs it. Audio that arrives before the stream exists is buffered and delivered in order, so a caller who talks over the opening is still transcribed.
- Sound cannot take the floor for the first 600ms. A caller already saying "hello?" as the leg connects has speech running before the agent has made a sound, and without the guard the first frame of the greeting would cut it — the caller would hear nothing at all, not even who answered. A keypress is exempt: it is unambiguous.
7 · A turn
Four states, and the loop between the last three is the conversation.
| State | What is happening |
|---|---|
| LISTENING | Transcription streams in. Partial results arm barge-in; final results accumulate into the turn. Turn detection decides when the caller has actually stopped rather than merely paused. |
| THINKING | Retrieval, if the agent is grounded, then the model. Tool calls happen here and can round-trip several times before anything is said. |
| SPEAKING | Audio is synthesised and written to the carrier as it is produced. The caller can interrupt throughout. |
Turn detection is not a fixed timer. A final transcript that reads as a finished sentence ends the turn outright; one that does not — "yes", a name, a date — ends it after a short silence instead of waiting for a second utterance that may never come. The clock measures silence rather than counting down, so a caller reading a number out slowly stays in one turn.
8 · Ending
A call ends in one of four ways, and all four write an end_reason:
- The caller hangs up. The carrier tells us.
- The agent ends it, by calling the
end_calltool. The reason it gives is written onto the call in its own words. - You end it, with
POST /v1/calls/{id}/end—ended_by_api. - A limit is reached —
max_duration, orno_inputafter the caller has gone quiet. The agent says a short goodbye first in both cases.
call.ended fires here and carries duration_seconds,
turns and end_reason. It does not carry the summary,
because the summary has not been written yet.
9 · Analysis
A few seconds after the call ends, the post-call pass runs over the transcript and writes a
summary and whatever typed fields the agent asks for. call.analysed fires when it
lands, carrying summary, extracted and the model that produced them.
See post-call analysis.
call.analysed, not
call.ended, if you want the summary. This is the single most common
integration bug against this API: code that reads summary in the
call.ended handler finds it empty every time, and looks like a platform fault.
The two events are seconds apart and both are delivered.Watching it happen
The whole sequence above, from one terminal — place the call, then follow every stage of it as it happens:
CALL=$(curl -s -X POST https://voice.sphoro.com/v1/calls \
-H "Authorization: Bearer $SPHORO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d "{\"agent_id\":\"$AGENT_ID\",\"to\":\"$YOUR_OWN_NUMBER\"}" | jq -r .id)
curl -sN "https://voice.sphoro.com/v1/calls/$CALL/events" \
-H "Authorization: Bearer $SPHORO_API_KEY"Three ways to do that in production, in increasing order of effort:
| Way | Use it for |
|---|---|
| Webhooks | Production. Signed, retried, delivered to your server. |
GET /v1/calls/{id}/events | Following one call as it happens, from a script or a page. A plain event stream. |
| The realtime socket | Live audio, or a feed of every call on the account at once. |