Graine AI

Call Lifecycle

What happens from POST /v2/calls to the finished record — every stage, which status you see, and exactly when each field becomes readable.

This page traces one outbound call from the API request to the fully-populated record. Inbound calls join at the ringing stage; everything after that is identical.

The flow

POST /v2/calls
   │
   ├─ scheduled_at set ──► scheduled ──(due time, calling window open)──┐
   │   (or: no free line,     ▲                                        │
   │    outside the window)   └── new scheduled_at ◄── window closed ──┤
   │                                                                   ▼
   └─ immediate ─────────► queued ──► initiated ──► ringing ──► in-progress
                                          │            │             │
                                          ▼            ▼             ▼ (hangup)
                                       failed    no-answer/busy   TERMINAL STATUS
                                                                (completed, …)
                                                                      │
                                              post-call processing (still running)
                                              transcript · cost · recording · analysis
                                                                      │
                                                                      ▼
                                                  all_processing_completed webhook

Stage by stage

  1. Accepted. POST /v2/calls mints the execution_id before it checks anything, returns it in the body and in the X-Execution-Id header, and answers one of three ways:
    • 201, status: "queued" — the dial was handed to the telephony network.
    • 202, status: "scheduled" — you sent scheduled_at; or the agent's calling window was shut (deferred_by_calling_guard: true); or every line was busy (reason: "waiting_for_a_free_line"). Nothing has dialled yet.
    • 202, status: "queued", dispatch: "pending_confirmation" — the dial went out and the acknowledgement did not come back. It may be ringing. Poll the id; never redial.
  2. Scheduled calls wait, and re-check the window. At fire time the agent's Calling Window is evaluated again; a call whose window has closed stays scheduled with a new scheduled_at inside the next open window rather than being rejected. Where in that window it lands is the agent's calling_guard.spread setting: by default held calls are scattered across the whole window, so a night's worth of them does not all ring at once. Set it to "asap" to ring every held call in the opening minute, or to a number of minutes to spread them across just that much of the window. A call waiting for a free line is dialled the moment one frees.
  3. Dialling. The call is handed to the telephony network: initiated, then ringing. A call nobody answers ends here as no-answer or busy. A call the person answers but our voice agent never joins ends failed, with an error.message beginning agent_unavailable (the webhook says sub_status: "agent_unavailable").
  4. Conversation. On answer the status is in-progress and the voice pipeline runs — transcription, the agent's reasoning, speech synthesis — one loop per conversational turn.
  5. Hangup ⇒ terminal status, immediately. When the call ends — whoever ended it, and why, is the subject of Hangups & Termination — the status moves straight to its terminal value and is_terminal becomes true. There is no intermediate "post-processing" status.
  6. Enrichment continues after the status is terminal. Transcript assembly, cost, the recording and analysis land in the seconds that follow.
  7. The webhook fires when everything has landed. The all_processing_completed event waits on all of it and carries the full 47-field record — with processing_incomplete and missing_fields naming anything that never arrived.

Terminal means the call is over — not that every field has landed

This is the one place this platform differs from the pattern many integrations expect. is_terminal: true the moment the line drops; cost, recording_url and transcript may still be null on that same read. If you poll, stop on is_terminal and the field you need being non-null. If you can, don't poll — the consolidated webhook exists precisely so you never have to guess when enrichment finished.

When the account runs out of credit

A call that has already connected is never cut off for credit: it runs to its end and is charged in full, even when that takes the balance below zero. What stops is everything new:

  • POST /v2/calls answers 402 with error 1400 once the balance is zero or below — for an immediate call and for one with scheduled_at alike (see error 1400 in Errors).
  • A scheduled call that comes due while the account is out of credit is held, not dropped: it stays scheduled, is checked again every few minutes, and is placed as soon as credits are added. If the account is still out of credit 60 minutes after the call was first held, the call ends with status balance-low and the all_processing_completed webhook reports status: "failed" with sub_status: "insufficient_balance".

When each field becomes readable

FieldAvailable
status, is_terminalImmediately at every stage.
duration_secondsAt hangup, from the carrier — the carrier figure is authoritative.
transcriptSeconds after hangup, once assembly completes. GET /v2/calls/{execution_id}/transcript answers status: "unavailable" while it is pending — never a 404.
cost.totalWhen billing runs, shortly after hangup. null means not yet billed; 0.0 means billed at zero (a voicemail, for example).
recording_urlWhen the recording is stored. A call that was never answered produces none.
summary, analysisWhen post-call analysis completes.
per-turn timingsTogether with the transcript. Read them at GET /v2/calls/{execution_id}/timing; see Execution Logs.

A canceled call that never dialled is the one case where these fields never arrive: nothing was placed, nobody was rung, no minutes are billed, and no webhook is sent.

Statuses

The full fifteen-value enum, the terminal / in-flight / failure sets, and the spelling rules live on Errors & Statuses. The webhook's own nine-value vocabulary is on Webhook Payloads.

On this page