sphoroVOICEdocs
Markdown

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 later

1–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:

CheckRefused with
The number is on your suppression list, consent is required and missing, or it is outside calling hours where the number actually isA compliance refusal naming which of the three. See compliance.
Your account is already at its concurrent-call ceilingThe call ends with end_reason: at_capacity. See concurrency.
The project this agent bills to is over its monthly budgetThe 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.

StateWhat is happening
LISTENINGTranscription 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.
THINKINGRetrieval, if the agent is grounded, then the model. Tool calls happen here and can round-trip several times before anything is said.
SPEAKINGAudio 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.

The stages overlap, which is the only reason the reply is fast. Synthesis of the first sentence starts before the model has finished the second, which itself started before the caller had finished being transcribed. Run the same stages one after another and the same reply takes about two seconds and sounds like a form. Latency has the budget.

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_call tool. The reason it gives is written onto the call in its own words.
  • You end it, with POST /v1/calls/{id}/endended_by_api.
  • A limit is reachedmax_duration, or no_input after 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.

Wait for 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:

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

WayUse it for
WebhooksProduction. Signed, retried, delivered to your server.
GET /v1/calls/{id}/eventsFollowing one call as it happens, from a script or a page. A plain event stream.
The realtime socketLive audio, or a feed of every call on the account at once.