completion_reason — the bot’s own
account of why the run ended. The field is sealed in the lifecycle.v1 contract and there are
ten values, no more.
This page is the reference for all ten. Troubleshooting is the companion:
it starts from a symptom you can see and walks to a fix. Start here when a run has already
ended and you want to know what ended it.
Despite the name,
completion_reason is carried on failed meetings as well as
completed ones. On a failed row it arrives alongside failure_stage
(requested · joining · awaiting_admission · active), which records how far the run got.Where to read it
completion_reason and failure_stage are top-level fields on every meeting the API returns —
no digging into data:
meeting.completed and bot.failed
webhooks under data.meeting, so you can route on the reason without polling.
GET /meetings/{id} carries them too.
The ten reasons
Whose side is the column that matters. Two of the ten point at Vexa; the rest are your own request, your own policy, or the ordinary friction of putting a bot into someone else’s calendar.Tuning the timeouts
Four of the reasons above are a clock running out, and every one of those clocks is yours to set. Passautomatic_leave on POST /bots (all values in milliseconds):
Reading a reason honestly
”Did not complete” is not a fault
A run that ended in anything other thancompleted has not, by that fact, told you anything
about whether Vexa worked. The user stopping the bot, nobody being in the room, a host declining
it, a lobby nobody watched — these are ordinary meeting outcomes, and eight of the ten values
above describe one. Only the reason separates them from the two that point at us, so a count of
non-completions with the reasons thrown away is not a reliability measure of anything — ours or
anyone’s.
join_failure is currently two failures under one name
join_failure today covers both a platform refusing the bot outright and a bot that was reaped
before any admission verdict arrived. They have different causes and different fixes, and the
value does not yet distinguish them. Tracked in
Vexa-ai/vexa#1058 — open. Until it is split,
treat a join_failure as “the bot did not get in”, not as a specific diagnosis.
left_alone is a silence verdict, not a headcount
The bot decides it is alone from the audio reaching it, not from a participant list. A meeting
where nobody spoke for the whole window ends this way with people still present — and so does a
meeting where the capture path failed and the bot genuinely heard nothing. If you see
left_alone on a call you know was busy, check whether any transcript segments were produced;
zero segments points at capture, not at an empty room. Tracked in
Vexa-ai/vexa#1192 — open.
completed is not proof of a transcript
A run can reach completed with a benign reason and still have produced no segments. The
completion reason describes how the bot’s run ended; it does not assert that transcription
worked. Check the transcript itself before treating a completed meeting as a delivered one.
Tracked in Vexa-ai/vexa#844 — open.
There is no per-reason rollup endpoint
The API serves the reason per meeting. It does not serve a breakdown across your meetings — if you want to know how your runs are ending in aggregate, pageGET /bots and tally the field
yourself.
The OpenAPI schema lists two values the product no longer emits
If you generate a client from the gateway’s OpenAPI document, itsMeetingCompletionReason enum
still carries stopped_before_admission and stopped_with_no_audio — 0.10-era values that the
0.12 codebase never produces. The ten above are the live set. Generated clients should tolerate
the two extras rather than depend on them.
Still stuck?
Troubleshooting covers the symptom-first path — a bot that will not join, a join with no transcript, a stack that will not come up. If a reason above does not match what you observed, that mismatch is worth reporting: tell us at Support with the meeting id, thecompletion_reason you got, and what you saw in the call.