> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.meetstream.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.meetstream.ai/_mcp/server.

# Debugging Bots

> Debug MeetStream meeting bots: the full status lifecycle, what NotAllowed / Denied / Failed mean, 202 still-processing responses, dashboard filters, and live webhook debugging with the CLI.

When a bot doesn't do what you expected, the answer is almost always in one of three places: the **status timeline**, the **webhook events**, or a **timeout that fired**. This page maps symptoms to causes.

## Start at the dashboard

The [All Bots view](https://app.meetstream.ai/bots) shows every bot with its live status, duration, and platform — filterable by lifecycle stage (Scheduled, Joining, In Waiting Room, In Meeting, Recording, …), date, and platform, with CSV export:

![All Bots view in the MeetStream dashboard: stats, lifecycle filters, and per-bot status chips](https://fdr-prod-docs-files-public.s3.us-east-1.amazonaws.com/meetstream-ai-573402.docs.buildwithfern.com/533db6320c8ff1ad8223ada4468656916b1af6652c19a7c0ce6e56a0aeb2a434/docs/assets/images/dashboard/dashboard-all-bots.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIA6KXJSKKNFOCF7G4B%2F20260814%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260814T144023Z&X-Amz-Expires=604800&X-Amz-Signature=eb3bdb3930bf480ff9a470f080d032f9e5f267fded72bfef9ffe182025921fb4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject)

For any single bot, `GET /api/v1/bots/{bot_id}/detail` returns the full session: platform, timings, **StatusTimeline**, the original request payload, and the `transcript_id`.

## The lifecycle

A successful recording bot moves through:

```
bot.joining → bot.in_waiting_room → bot.inmeeting → bot.recording
→ bot.leaving → bot.stopped
→ audio.processed → transcription.processed → video.processed → bot.done
```

Key semantics:

* Exactly **one terminal in-meeting event** per bot: `bot.stopped`, `bot.kicked`, `bot.denied`, `bot.notallowed`, or `bot.failed`.
* `bot.stopped` carries a `bot_status` that says why: `Stopped` (normal), `NotAllowed`, `Denied`, or `Error`.
* `bot.kicked` carries `bot_status: "Stopped"` — branch on the **event name** to detect kicks.
* Clean terminals use `status_code: 200`; failure terminals use `status_code: 500`. Failure `message` strings are prefixed `"Error: ..."` (unexpected) or `"Failed: ..."` (handled: validation, timeout, denial) — useful for alert routing.
* `bot.done` always has `status_code: 200`: it means the post-call pipeline **finished**, not that every step succeeded — check the individual artifact events.
* On Zoom only, `bot.recording_permission_allowed` / `denied` fire between `bot.inmeeting` and `bot.recording` — see [Zoom bots](/guides/platforms/zoom).

Live integrations receive the envelope under the `event` key. Older payloads may use `bot_event` — the [CLI](/build-with-ai/meetstream-cli) and [MCP server](/build-with-ai/meetstream-mcp-server) implement the live-verified `event` model.

## Symptom → cause

| Symptom                                                     | What actually happened                                                                                                                                                                                           | Where to confirm                                                                                                            |
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| "The bot never showed up"                                   | It's usually in the lobby: on Google Meet with Host management on, regular attendees can't even see the admission request                                                                                        | Status shows `InWaitingRoom`; see [lobby guide](/guides/app-integrations/gmeet-lobby-admission)                             |
| Terminal `bot.notallowed`                                   | Nobody admitted the bot before `waiting_room_timeout` (defaults: Meet/Zoom 600s, Teams 1200s)                                                                                                                    | Raise the timeout, or use a [signed-in bot](/guides/app-integrations/google-signed-in-bots) on the invite to skip the lobby |
| Terminal `bot.denied`                                       | A human explicitly rejected the join request                                                                                                                                                                     | Nothing to fix in code — it's a host decision                                                                               |
| Terminal `bot.failed`                                       | Join failure or crash; message prefix tells you `Failed:` (handled) vs `Error:` (unexpected)                                                                                                                     | On Zoom, check dev-mode credentials and password-protected links                                                            |
| `bot.leaving` right after `bot.joining`, no `bot.inmeeting` | The join failed before entering the meeting                                                                                                                                                                      | Check the meeting link is valid and live                                                                                    |
| Bot left "on its own" mid-meeting                           | A timeout fired: `everyone_left_timeout`, `voice_inactivity_timeout`, `noone_joined_timeout`, or `in_call_recording_timeout` — whichever expires first wins                                                      | The `bot.stopped` message names the trigger; tune in [automatic leave](/guides/features/automatic-leave-configuration)      |
| Bot left when another notetaker joined                      | Your `bot_detection.using_participant_names` matched                                                                                                                                                             | The stop message contains `bot_detection.using_participant_names`                                                           |
| Zoom bot joined but never recorded                          | Host didn't grant recording permission within `recording_permission_denied_timeout` (60s default)                                                                                                                | Clean `bot.stopped` after `bot.recording_permission_denied`                                                                 |
| Audio/video endpoint returns **202**                        | Artifacts aren't ready: per-participant streams return 202 while the bot is still in the meeting or processing (`stage` field says where it is)                                                                  | Poll again in 10–30 seconds                                                                                                 |
| Transcript "not ready" after `bot.stopped`                  | Post-call transcription still running — or you used a **streaming-only** provider (`deepgram_streaming`, `assemblyai_streaming`, `meeting_captions`), which never produces a post-call `transcription.processed` | Retry shortly; for streaming providers, your live webhook already has the text                                              |
| Artifact endpoints return 404/410 on an old bot             | Retention expired or the data was deleted — status is `MediaExpired`, and a `data_deletion` event fired                                                                                                          | See [usage & retention](/guides/features/usage-and-retention)                                                               |
| Webhook never arrived                                       | Delivery is best-effort: non-2xx responses and unreachable URLs are **not retried**                                                                                                                              | Test locally with `meetstream listen` + ngrok                                                                               |

## Watch it live

Two fast feedback loops while developing:

```bash
# 1. Pretty-print every webhook event as it happens
meetstream listen --port 3333          # then: ngrok http 3333
meetstream bot create "<link>" -c https://<ngrok>.ngrok.io/webhook

# 2. Poll a bot to a terminal state
meetstream bot status <bot_id> --watch
```

Or let your agent do it: the [MeetStream MCP server](/build-with-ai/meetstream-mcp-server) exposes bot status, details, and transcripts as tools, so Claude can debug a bot session for you.

## Status reference

Wire values you'll see in `GET /bots/{id}` and webhook payloads:

`Scheduled` · `Joining` · `InWaitingRoom` · `InMeeting` · `Recording` · `RecordingPermissionAllowed` · `RecordingPermissionDenied` · `Leaving` · `Stopped` · `NotAllowed` · `Denied` · `Error` · `MediaProcessing` · `Done` · `MediaExpired`

`MediaProcessing` (post-call pipeline running) has no webhook of its own; `Done` corresponds to `bot.done`, `MediaExpired` to `data_deletion`.

## Next steps

* [Webhooks and Events](/guides/webhooks/webhooks-and-events) — full payload reference
* [Automatic Leave Configuration](/guides/features/automatic-leave-configuration) — every timeout, range, and default
* [Set Up Local Server for Webhook](/guides/webhooks/local-webhook-server)