Three fields carry the answer: Vapi’s endedReason, Retell’s disconnection_reason, and Twilio’s call status plus SipResponseCode. We counted 597 unique endedReason values in Vapi’s published OpenAPI spec on 8 September 2026, against 34 Retell reasons and 8 Twilio call statuses. Same incident, three vocabularies. Start with the field name, not the symptom.
Why did my AI voice agent call drop? Find the field before you form a theory
Every managed voice platform writes a terminal code on the call record, and every platform uses a different name for the field and a different vocabulary inside it. Before you can diagnose anything you need the string itself, not a description of what the caller experienced:
- Vapi writes
endedReasonon the Call object and shows it in the Ended Reason column of the call logs (Vapi, Call ended reasons). - Retell AI writes
disconnection_reason, visible on the call in Call History or via the Get Call API (Retell, Debug call disconnection). - Twilio, sitting underneath both as a telephony carrier, writes a call
Statusand — on terminal status callbacks only — aSipResponseCode(Twilio, Call Resource).
The first thing the field usually tells you is that nothing went wrong. Twilio’s own documentation is explicit that A completed call indicates that a connection was established, and audio data was transferred. This can occur when a call is answered by a person, an IVR phone tree menu, or even a voicemail.
A completed Twilio leg with a 12-second duration and a Vapi endedReason of customer-ended-call is not an incident. It is a person hanging up.
That ordering matters, because a working system produces expected-behaviour codes by design — customer-ended-call, user_hangup, assistant-ended-call, agent_hangup — and both Vapi and Retell classify those as normal endings rather than errors. Rule them out first, then work down the list below. We hold no cross-platform incidence data and could not locate a published dataset of it, so this page is ordered by the shape of the failure rather than by any claim about how often each one occurs.
The Rosetta table: Vapi endedReason, Retell disconnection_reason and Twilio status side by side
Every string in this table was read from the vendor’s own documentation or published API schema. Where we could not locate a published equivalent in that vendor’s own documentation or schema, the cell says so rather than guessing — an invented equivalence is worse than a gap, because it sends you to the wrong dashboard.
| What happened | Vapi endedReason |
Retell disconnection_reason |
Twilio status / SIP code | Likely root cause | Where to look next |
|---|---|---|---|---|---|
| Caller hung up | customer-ended-call |
user_hangup |
completed |
Normal | The last 20 seconds of transcript, not the logs |
| Agent hung up deliberately | assistant-ended-call, assistant-said-end-call-phrase |
agent_hangup |
completed |
End-call tool fired, or an end-call phrase matched | The end-call phrase list and the tool’s trigger conditions |
| Silence timeout | silence-timed-out |
inactivity |
completed — no published equivalent located for the timeout itself |
The configured silence window elapsed with no speech detected | Vapi silenceTimeoutSeconds; Retell end_call_after_silence_ms |
| Call hit its length cap | exceeded-max-duration |
max_duration_reached |
completed — no published equivalent located |
Platform-side duration limit, not a fault | Vapi maxDurationSeconds; Retell max call duration |
| Answered by voicemail | voicemail |
voicemail_reached |
completed (Twilio documents voicemail as a completed call) |
Answering-machine detection fired, or was not configured | Voicemail-detection settings on the assistant |
| Line busy | customer-busy |
dial_busy |
busy |
Destination engaged | Retry cadence, not configuration |
| Rang out | customer-did-not-answer |
dial_no_answer |
no-answer; Twilio returns 487 when the call does not connect before the Timeout value elapses |
Nobody picked up inside the ring window | The dial timeout, then the time of day you are calling |
| Number invalid or unreachable | twilio-reported-customer-misdialed |
invalid_destination |
failed; Twilio returns 404 when the destination number is unreachable |
Malformed number, usually a formatting problem rather than a dead line | E.164 formatting, then the provider’s route to that country |
| Carrier blocked the call as spam | No published equivalent located | marked_as_spam |
No published equivalent located in Twilio’s call status values | Number reputation, typically after a volume spike with a low pickup rate | Number warm-up, pickup rate, and branded-calling registration |
| SIP trunk rejected the call | call.in-progress.error-providerfault-outbound-sip-403-forbidden |
telephony_provider_permission_denied |
SIP 403 Forbidden in response to your INVITE |
Credentials, IP allowlist, caller-ID permission or geographic permission | The Via header on the 403 — Twilio documents its received parameter as showing the IP address the SIP request came from — then your trunk’s access control list |
| Concurrency ceiling hit | call.start.error-subscription-concurrency-limit-reached |
concurrency_limit_reached |
Twilio documents 503 Trunk concurrent call limit exceeded on Elastic SIP Trunking |
Plan or trunk limit, not a fault in the agent | Your plan’s concurrency allowance and your dialer’s pacing |
| Billing stopped the call before it started | call.start.error-subscription-insufficient-credits, call.start.error-subscription-frozen |
no_valid_payment |
No published equivalent located | Wallet, card or credit balance | The billing page, before you debug anything else |
| Transfer failed | call.in-progress.error-transfer-failed, call.in-progress.error-warm-transfer-silence-timeout |
transfer_cancelled (published for the transfer agent’s leg of an agentic warm transfer; no general transfer-failure reason located) |
No single status — the transfer becomes its own leg with its own status | Bad destination, unsupported transfer scenario, or the destination not answering with audio | The child leg in the provider’s call log, matched on the telephony call ID |
| Telephony provider closed the audio socket | phone-call-provider-closed-websocket, call.in-progress.twilio-completed-call |
No published equivalent located | completed with a non-zero duration |
The media path between the platform and the carrier broke | Provider call timeline, compared timestamp-to-timestamp against the platform’s |
| No audio arriving from the caller | call.in-progress.error-assistant-did-not-receive-customer-audio |
error_no_audio_received |
in-progress, then completed |
Network, microphone, or a caller who disconnected silently | Whether the transcript is empty or merely stops |
| Platform-side process crashed | call.in-progress.error-vapifault-worker-died, worker-shutdown |
error_retell |
completed on the carrier leg |
Vendor infrastructure; usually transient | Retry, then escalate with the call ID if it recurs |
| Custom LLM never connected | call.in-progress.error-pipeline-ws-model-connection-failed |
error_llm_websocket_open |
Not visible to the carrier | Wrong URL, DNS, TLS, route mismatch, or nothing listening | Your own server’s access log for the upgrade request |
| Custom LLM socket dropped mid-call | call.in-progress.error-providerfault-custom-llm-llm-failed (generic custom-LLM failure; no socket-specific mid-call value located) |
error_llm_websocket_lost_connection |
Not visible to the carrier | Keepalives stopped, or an idle timeout on a proxy in front of your server | Your proxy’s WebSocket idle timeout; with Retell’s auto_reconnect enabled you must echo ping_pong within 5 seconds |
| Speech-to-text failed | pipeline-error-deepgram-transcriber-failed and the equivalent per-provider strings |
error_asr |
Not visible to the carrier | Transcriber credentials, quota, or a provider outage | The transcriber provider’s status page and your key’s quota |
Two things fall out of building this. First, the platforms agree almost perfectly on the boring rows — hang-ups, busy, no answer, voicemail, duration caps — and diverge completely on failures. Second, the direction of detail is not symmetrical: Vapi names the failing component and the responsible party in the string itself, while Retell keeps a short, stable list and pushes detail into the call’s detailed logs and PCAP file. Neither is better; they just require different next moves from you.
The triage flow: four failure shapes, four different next checks
A caller only ever reports one of four things. Each shape has a different first check, and running the wrong one costs you the incident window.
Branch 1 — Dead air: the line was open but nobody spoke
- Read the terminal code. If it is
silence-timed-outorinactivity, the platform ended a call in which it detected no speech. Next check: the configured silence window, then whether the transcript is empty or merely truncated. - If the transcript is empty and the code is
call.in-progress.error-assistant-did-not-receive-customer-audioorerror_no_audio_received, audio never reached the platform. Next check: the carrier leg’s duration — a longcompletedleg with no inbound media points at the media path, not the agent. - If the transcript has caller turns but no agent turns, the pipeline broke on the way out. Next check: the code’s provider segment — a
*-voice-failed,*-transcriber-failedor*-llm-failedstring names which of the three services to open first. Long pauses that are not a failure are a different problem; see our note on what sub-second voice AI response times actually require.
Branch 2 — The agent hung up
- If the code is
assistant-ended-calloragent_hangup, this was deliberate. Next check: the tool-call entry immediately before the end of the transcript. - If it is
assistant-said-end-call-phrase, a configured phrase matched. Next check: your end-call phrase list against the agent’s last utterance, looking for a phrase common enough to fire mid-conversation. - If it is
exceeded-max-durationormax_duration_reached, you hit a cap. Next check: the duration value on the call against the configured maximum — they should match to the second. - If it is
voicemailorvoicemail_reached, answering-machine detection fired. Next check: the recording, to confirm it really was a machine.
Branch 3 — The transfer failed
- Confirm the platform actually initiated it. On Vapi, a successful hand-off is
assistant-forwarded-call; anything else means the transfer never started. Next check: the transfer tool’s configuration, and whether call control has been handed to your own server. - Confirm the scenario is supported. Vapi publishes a support matrix in which web-call-to-phone-number and PSTN-to-SIP transfers are listed as not supported. Next check: that matrix, against the leg types in your call.
- If the platform initiated it and the shape is supported, the failure is downstream. Next check: the child leg in the carrier’s call log, found by the telephony call ID on the platform’s call object — not by timestamp. What the human on the other end receives when it does work is a separate design problem, covered in our piece on carrying context through an AI-to-human handoff.
- For SIP trunks, the transfer is a REFER. Next check: whether a REFER left at all, and what the response to it was.
Branch 4 — The provider closed the socket underneath the call
- The signature is a partial transcript that stops mid-sentence with a non-zero duration and a transport-level code:
phone-call-provider-closed-websocket,call.in-progress.twilio-completed-call, orcall.in-progress.sip-completed-call. Next check: the carrier’s own timeline for that leg, because the carrier will show a normal completion where the platform shows a break. - Distinguish it from a platform crash.
call.in-progress.error-vapifault-worker-diedandworker-shutdownare the platform’s own process ending, not the carrier’s. Next check: whether other calls in the same minute ended the same way — a deployment-shaped cluster looks different from a network-shaped single event. - For custom LLM integrations, the socket that closed may be yours. Next check: the WebSocket close code in your server’s logs. Retell documents that a close code of
1000from your side is treated as a deliberate hangup and ends the call asagent_hanguprather than as an error, which is how a server-side crash gets misfiled as a normal call. - If the socket closed before any audio flowed on an inbound call, the problem is upstream of the agent entirely — see the checks in our guide to getting a phone line ready for an inbound AI caller.
The single most useful habit is recording the platform call ID and the telephony call ID together at the end of every call. Without the pairing, branch 3 and branch 4 both stall at the same place: you can see that the carrier did something, but you cannot find which leg it did it to.
Zian AI runs live phone, SMS, email and WhatsApp outreach through its own digital team agents, and reading these codes is part of running them. Zian is in a partnership-application beta.
How big each vocabulary actually is, and how we counted
Counts matter here because they tell you how much of the diagnosis the vendor has pre-classified for you and how much you have to reconstruct. Method: on 8 September 2026 we fetched each vendor’s published machine-readable schema or documentation page and counted the enumerated values in the relevant field. No estimates, no rounding.
| Platform | Field | Values counted | Source counted |
|---|---|---|---|
| Vapi | endedReason |
629 entries, 597 of them unique | The Call schema in the OpenAPI document at api.vapi.ai/api-json |
| Retell AI | disconnection_reason |
34 in the schema; 33 described in the docs table | The DisconnectionReason enum in Retell’s Get Call API reference |
| Twilio | call Status |
8 | The Call Status Values table on Twilio’s Call Resource page |
| Bland AI | status and queue_status |
6 and 9 | Bland’s Call Details API reference |
| ElevenLabs Agents | conversation status |
5 | ElevenLabs’ Get conversation details API reference |
Three findings worth carrying away. The Vapi enum contains 32 duplicate entries — 629 listed values resolve to 597 distinct strings — so any count taken straight off the spec without deduplicating runs about 5% high. The Retell schema publishes one value, manual_stopped, that we did not find described in the documentation table. And the shape of the Vapi enum is itself diagnostic: 255 of the 597 distinct values (42.7%) begin pipeline-error-, while only 10 mention SIP and only 10 mention a transfer. A managed voice platform expects to fail at the model, voice and transcription layer far more variously than at the telephony layer — which is the opposite of where most teams start looking.
SIP response codes: the only vocabulary all of these platforms share
Underneath every one of these products is SIP, and SIP codes are the one layer that means the same thing everywhere. Vapi states this plainly in its Troubleshoot SIP response codes guide: Session Initiation Protocol (SIP) response codes describe the result of a SIP request. They are standard protocol responses, not Vapi-specific error codes.
The same guide warns that Vapi does not have a code-specific
, which is exactly why the mapping above has blanks in it.endedReason for every SIP response
| SIP code | RFC 3261 name | What it usually means in a voice-agent incident | First check |
|---|---|---|---|
403 |
Forbidden | The responding system understood the request and refused it | Credentials, IP allowlist, caller-ID and geographic permissions |
404 |
Not Found | The number or SIP URI could not be resolved; Twilio returns this when the destination is unreachable | Number formatting, then whether the provider has a route |
407 |
Proxy Authentication Required | A proxy is challenging; can be a normal step rather than a failure | Whether an authenticated request followed the challenge |
408 |
Request Timeout | No response produced in time | DNS, firewall rules, signalling ports and transport |
480 |
Temporarily Unavailable | Destination known but offline, unregistered or on do-not-disturb | Endpoint registration and routing |
484 |
Address Incomplete | The dialled address is incomplete | Country code and E.164 formatting |
486 |
Busy Here | Reached, but cannot take another call | Nothing on your side; this is destination state |
487 |
Request Terminated | Cancelled before completion; Twilio returns this when its Timeout elapses |
Who sent the CANCEL, from the signalling trace |
503 |
Service Unavailable | Temporarily unavailable — and providers also use it for routing failures and call limits | Trunk capacity, calls-per-second limits, and any Retry-After header |
Names in the second column are as defined in RFC 3261 §21. The third and fourth columns follow the guidance Vapi publishes in its SIP response code guide and the quick reference Retell publishes for debugging SIP calls from a PCAP file. A practical consequence for anyone running volume: 503 from a trunk usually means a limit rather than an outage, and limits are a capacity-planning problem rather than an incident — the distinction we drew when writing about scaling from 100 to 10,000 calls a day.
Where the mapping runs out, and what we could not find
This is documentation mapping, so the gaps are part of the result.
- ElevenLabs Agents. We could not locate a published enum of termination reasons. The Get conversation details API reference types
termination_reasonas a string with no allowed-values list — it sits inside themetadataobject — while the top-levelstatusfield on the same response does publish five allowed values:initiated,in-progress,processing,done,failed. We looked at that reference page and at the documentation index atelevenlabs.io/docs/llms.txt. - Bland AI. The Call Details reference publishes six
statusvalues and ninequeue_statusvalues, but describeserror_messageonly as a description of the error. We could not locate an enumerated list of those messages on that page or in the documentation index atdocs.bland.ai/llms.txt; the page gives four examples of common failure text rather than a closed set. - Spam blocking. Retell publishes
marked_as_spam. We could not locate an equivalent published value in Vapi’sendedReasonenum or in Twilio’s call status values, having searched the 597 distinct Vapi strings and the Call Resource status table. Vapi’scall.start.error-fraud-check-failedis its own fraud detection blocking a call at start, which is a different event from a carrier refusing to deliver one. - Twilio and the pipeline. Nothing that happens to the model, voice or transcriber is visible to a carrier at all. The 255
pipeline-error-values alone — 43% of Vapi’s vocabulary — describe events Twilio has no way to represent.
The general rule the exercise produced: a code tells you which vendor’s dashboard to open next; it rarely tells you the cause. Treat the string as a router, not as a diagnosis, and the four-branch flow above as the thing that actually narrows it. More of how we work through evidence like this sits in the Zian AI questions and answers hub.
Frequently asked questions
Why did my AI voice agent call drop with no error code?
Because most terminal codes are not errors. Vapi groups customer-ended-call, assistant-ended-call, silence-timed-out, exceeded-max-duration and voicemail as expected behaviour, and Retell marks user_hangup, agent_hangup and inactivity the same way. Twilio adds a further trap: it documents that a completed call simply means a connection was established and audio was transferred, including when the answer was a voicemail or an IVR menu.
Does a SIP 487 mean the caller hung up?
Not necessarily — it means somebody cancelled. RFC 3261 §21.4.25 defines 487 Request Terminated as: The request was terminated by a BYE or CANCEL request. This response is never returned for a CANCEL request itself.
In practice the CANCEL is often yours: Twilio’s Call Resource documentation states that if the call does not connect before the specified Timeout value elapses, the API returns 487. Read the signalling trace to see which side sent it before concluding the person rejected your call.
Is there a standard set of call-ended codes across voice AI platforms?
We could not locate one. Each platform publishes its own vocabulary in its own field name, and the counts are not comparable — 597 distinct values for Vapi against 34 for Retell is a difference in design philosophy, not in reliability. The only shared layer is SIP, and even there, Vapi notes it does not have a code-specific ended reason for every SIP response. We looked at Vapi’s, Retell’s, Twilio’s, Bland AI’s and ElevenLabs’ published documentation and API schemas on 8 September 2026.
How do I tell a silence timeout from a media failure?
By whether the platform heard anything at all. A silence timeout — silence-timed-out on Vapi, inactivity on Retell — means the timer expired with no speech detected, and the transcript will usually contain earlier turns. A media failure — call.in-progress.error-assistant-did-not-receive-customer-audio or error_no_audio_received — means audio stopped arriving after the connection was established, and the carrier leg will show a healthy duration with nothing to show for it. The two need opposite fixes: one is a settings change, the other is a network investigation.
What does phone-call-provider-closed-websocket mean?
Vapi documents it as the call provider’s WebSocket connection closing unexpectedly, with the caller experiencing an abrupt drop. The important property is that it is a statement about the audio path between the platform and the carrier, so the evidence lives in the carrier’s logs rather than in the assistant’s configuration. We could not locate a directly equivalent value in Retell’s published DisconnectionReason enum; its nearest published value, error_no_audio_received, describes media that stops arriving rather than a socket that closed.
Which field should I log for every call?
Three: the platform’s terminal code, the platform’s call ID, and the telephony provider’s call ID. Vapi exposes the last of these as phoneCallProviderId on the Call object. Without the pairing, a transfer failure or a carrier-side disconnect cannot be traced to the specific leg it happened on, and the diagnosis stalls at the point where you can see that something happened but not to what.
Zian AI builds autonomous AI sales agents — outbound appointment setters, 24/7 customer support agents in 30+ languages, and sales call closers — with SmartReach AI™ orchestrating channel and timing and PrecisionPitch AI™ split-testing the scripts. The platform is in a partnership-application beta.