For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Contact Support
GuidesAPI ReferenceBuild with AI
GuidesAPI ReferenceBuild with AI
  • Introduction
    • Meetstream AI Overview
    • Meeting Platforms Supported
  • Get Started
    • Dashboard Setup
    • Create your First Bot
  • Bot Features
    • Idempotency Keys
    • Automatic Leave Configurations
  • App Integrations
    • Zoom Marketplace App Setup
    • Zoom App Production Submission
    • Zoom OBF Implementation
    • Google Signed-In Bots
  • Calendar Integrations
    • Google Calendar OAuth Setup
    • Outlook Calendar Setup
  • Transcription & Recordings
    • Create Bot with Post Call Transcription
    • Create Bot with Live Transcription
    • Create Bot with Per Participant Video
    • Create Bot with Per Participant Audio
  • Webhooks
    • Webhooks and Events
    • Set Up Local Server for Webhook
  • MIA (Meetstream Infrastructure Agents)
    • Create MIA
    • MIA Configurations
  • WebSockets
    • Real-time Audio Streaming
    • Real-time Video Streaming
    • Meeting Control and Command Patterns
    • Bridge Server Architecture
LogoLogo
Contact Support
On this page
  • 1) Why this exists
  • 2) How it works (one paragraph)
  • 3) Quick example
  • 4) Field reference
  • automatic_leave.bot_detection
  • automatic_leave.bot_detection.using_participant_names
  • Validation rules
  • 5) Behaviour timeline
  • 6) Choosing matches
  • 7) Tuning activate_after and timeout
  • 8) Self-identity: how the bot knows which row is itself
  • 9) The other automatic_leave timeouts
  • waiting_room_timeout
  • noone_joined_timeout
  • everyone_left_timeout
  • voice_inactivity_timeout
  • in_call_recording_timeout
  • recording_permission_denied_timeout (Zoom only)
  • Putting them together
  • 10) Common pitfalls
  • ”My bot is leaving meetings too early.”
  • ”My bot isn’t leaving even though only notetakers remain.”
  • ”How do I disable it on a scheduled bot?”
  • 11) FAQ
  • 12) Related docs
Bot Features

Guide to Configure Various Automatic Leave Triggers for the Bot

||View as Markdown|
Was this page helpful?
Previous

Idempotency Keys

Next

Zoom Bot Implementation Guide

Built with

This guide explains how to make your MeetStream bot automatically leave a meeting when only other vendor notetakers remain — Otter, Fireflies, tl;dv, Read.ai, Fathom, Grain, Copilot, Notta, and so on.

It’s an opt-in nested mechanism inside the existing automatic_leave payload field on POST /api/v1/bots/create_bot (and honored identically when you PATCH a scheduled bot).

Scope: Works on all three supported platforms — Google Meet, Zoom, and Microsoft Teams. Same payload shape everywhere.

Section 9 also covers the rest of the automatic_leave knobs — waiting_room_timeout, noone_joined_timeout, everyone_left_timeout, voice_inactivity_timeout, in_call_recording_timeout, and recording_permission_denied_timeout — since bot_detection interacts with all of them.


1) Why this exists

automatic_leave.everyone_left_timeout only fires when the participant count drops to zero. In practice, a single lingering vendor notetaker (Otter, Fireflies, tl;dv, …) keeps the participant count at one and pins your bot to the meeting:

  • The bot keeps recording empty audio.
  • You keep accruing recording minutes.
  • The transcript fills with crosstalk between notetakers, or with silence.
  • A Fargate task stays warm until in_call_recording_timeout (default 4 hours) finally pulls the plug.

Turning on bot_detection lets the bot recognise that every remaining participant looks like another notetaker, wait a short countdown to be sure, and then leave cleanly.


2) How it works (one paragraph)

You give the bot a list of bot-name keywords (e.g. ["otter", "fireflies", "tl;dv"]). After the bot has been admitted into the meeting for activate_after seconds, it begins checking the roster on every change. Whenever every remaining non-self participant’s display name contains at least one of those keywords, the bot arms a timeout-second countdown. If a human reappears before the countdown fires, the timer is cancelled. If the condition still holds when the countdown fires, the bot leaves the meeting and you get the usual bot.leaving → bot.stopped webhook sequence.

The bot never matches itself against the keyword list — it identifies its own row in the roster via a stable platform identity (Google Meet deviceId / Zoom user_id / Teams bot_id), not its display name. Naming your bot MeetStream Notetaker will not cause a false positive even if notetaker is in matches.


3) Quick example

$curl -X POST "https://api.meetstream.ai/api/v1/bots/create_bot" \
> -H "Authorization: Token <YOUR_API_KEY>" \
> -H "Content-Type: application/json" \
> -d '{
> "meeting_link": "https://meet.google.com/abc-defg-hij",
> "bot_name": "MeetStream",
> "automatic_leave": {
> "everyone_left_timeout": 600,
> "bot_detection": {
> "using_participant_names": {
> "matches": [
> "notetaker", "recorder", "assistant", "copilot",
> "otter", "fireflies", "tl;dv", "read.ai",
> "fathom", "grain", "fellow", "notta", "krisp"
> ],
> "activate_after": 300,
> "timeout": 30
> }
> }
> }
> }'

What that payload says, in plain English:

“Five minutes after I’m admitted to the meeting, start watching the roster. The moment every other participant looks like a notetaker, give them 30 seconds to prove otherwise — and if they don’t, leave.”


4) Field reference

All bot_detection fields live under automatic_leave.bot_detection on the create-bot payload.

automatic_leave.bot_detection

FieldTypeRequiredDescription
using_participant_namesobjectYes (when bot_detection is present)Detect bots by matching their display name against a keyword list. See below.

Other mechanisms from Recall.ai’s spec — using_participant_events (no-speaker / no-screen-share heuristic) and silence_detection (audio-energy fallback) — are not yet supported. Passing them returns HTTP 400 so we don’t silently ignore them.

automatic_leave.bot_detection.using_participant_names

FieldTypeRequiredDefaultRangeDescription
matchesarray of stringYes—non-empty listCase-insensitive substrings. A participant is treated as a bot when their display name contains any of these substrings. Stored lowercased and deduped.
activate_afterint (seconds)No30060–1800Delay before the check becomes active, counted from when the bot is admitted to the meeting. Lets real participants trickle in before the check arms.
timeoutint (seconds)No55–300Once every remaining participant matches, how long to wait before actually leaving. The timer resets if a human reappears.

Validation rules

  • matches must be a non-empty array of non-empty strings. Whitespace is trimmed, casing is normalised to lowercase, duplicates are dropped — so ["Otter", "otter", " OTTER "] becomes ["otter"] server-side.
  • activate_after and timeout are strict integers (no booleans, no numeric strings). Out-of-range values return HTTP 400.
  • The block is opt-in: omit bot_detection (or omit using_participant_names) to disable. The rest of automatic_leave continues to apply.
  • Validation runs in two paths: POST /api/v1/bots/create_bot and PATCH /api/v1/calendar/scheduled_bots/{bot_id}. They share the same schema, so anything that’s valid at create time is valid at patch time.

5) Behaviour timeline

Imagine a meeting that opens at t=0 and your bot is admitted at t=2s with activate_after=300, timeout=30:

TimeRosterWhat the bot does
t=0–2s(joining)No check yet.
t=2sBot admitted, _in_meeting_since clock starts.Check is dormant until t=302s.
t=10sBot + 2 humans + 1 Otter notetaker.Dormant.
t=180sBoth humans leave; only bot + Otter remain.Still dormant — activate_after hasn’t elapsed.
t=302sBot + Otter still in meeting.Condition holds → arms a 30s timer.
t=320sLate human joins.Condition broken → timer cancelled.
t=400sThat human leaves again; only bot + Otter remain.Condition holds again → re-arms a fresh 30s timer.
t=430sStill bot + Otter.Timer fires → bot leaves the meeting.

What you see externally:

  • A bot.leaving webhook event followed by bot.stopped (terminal). The message field on the underlying status update will mention bot_detection.using_participant_names so you can attribute the exit.
  • No bot.kicked / bot.denied / bot.failed — this is a graceful, customer-configured exit, not a platform-side eject.
  • Recording, transcription, and the rest of the post-call pipeline run as normal. You’ll still receive audio.processed, transcription.processed, video.processed, and bot.done after the artifacts are ready.

See Bot Lifecycle Webhook Events for the full webhook sequence.


6) Choosing matches

The keywords below are a good starting baseline that catches most common notetakers in the wild. Substring matches, case-insensitive:

1[
2 "notetaker", "recorder", "assistant", "copilot",
3 "otter", "fireflies", "tl;dv", "read.ai",
4 "fathom", "grain", "fellow", "notta",
5 "krisp", "meetgeek", "supernormal", "tactiq",
6 "rewatch", "circleback"
7]

Tips:

  • Match the visible display name, not a brand. Most notetakers join with a name like "Otter.ai Notetaker", "tl;dv Bot", "Fathom AI Notetaker", etc. — picking up either the brand fragment (otter) or the role fragment (notetaker) is usually enough.
  • Substring, not exact. "otter" matches "Otter.ai Notetaker", "OtterPilot", and "otter-bot" — you don’t need separate entries.
  • No regexes. This is plain lowercase(name).contains(keyword) per keyword. Special characters (., :, ;) are matched literally.
  • Don’t include your own bot name. It’s safe (the bot excludes itself by stable identity) but it’s noise.

7) Tuning activate_after and timeout

Profileactivate_aftertimeoutWhen to use
Aggressive60–1205–30Internal meetings where you know the call kicks off quickly. Faster reaction, slightly higher false-positive risk if a human joins late.
Balanced (recommended)30030–60Default. Gives stragglers five minutes to arrive, then leaves within a minute of a roster going bot-only.
Conservative600–120060–300Customer-facing meetings or webinars where it’s important to avoid leaving early. Strongly biases toward staying.

Rule of thumb: err on the side of larger values. A false negative (bot stays when it should leave) is recoverable — the existing everyone_left_timeout, in_call_recording_timeout, and voice-inactivity fallbacks will eventually fire. A false positive (bot leaves while real people are still in the meeting) is not.


8) Self-identity: how the bot knows which row is itself

Each platform exposes a different stable identity for the bot’s own row in the roster. You don’t have to do anything — this is handled automatically — but it’s useful to understand because it explains a subtle edge case in Google Meet.

PlatformSelf-identity usedNotes
Google MeetdeviceId (captured from the (You) marker in the Meet UI on join)Captured non-blocking. If capture hasn’t completed (rare), the check fails closed — it skips that tick and cancels any armed timer, then retries on the next tick.
Microsoft Teamsbot_id (stable from the bot’s TeamsBotConfig)Available as soon as the websocket is connected.
Zoommy_participant_id returned by the Zoom SDKAvailable as soon as the bot joins the participants list.

Because of this, naming your own bot to match a matches keyword is safe across all three platforms — the self row is excluded by ID, never by name.

Google Meet caveat: in the rare case two of your own bots launch into the same meeting at almost the same instant and their self-identity capture races, the bot may briefly perceive its sibling as “another notetaker”. The fail-closed safety means it will not leave on uncertain identity — but if you regularly send two bots to the same meeting, set activate_after ≥ 300s so the capture has fully settled by the time the check arms.


9) The other automatic_leave timeouts

bot_detection runs alongside the existing timeouts on the automatic_leave object — it doesn’t replace any of them. They each cover a different failure mode and they can all be configured in the same payload:

1"automatic_leave": {
2 "waiting_room_timeout": 600,
3 "noone_joined_timeout": 600,
4 "everyone_left_timeout": 300,
5 "voice_inactivity_timeout": 100,
6 "in_call_recording_timeout": 14400,
7 "recording_permission_denied_timeout": 60,
8 "bot_detection": { /* … */ }
9}

Field-by-field reference below. All values are integer seconds.

waiting_room_timeout

What it controlsMax time the bot will sit in the waiting room / lobby before giving up and exiting.
Range60 – platform max (GMeet: 600, Zoom: 1200, Teams: 1800). Out-of-range returns HTTP 400.
Default600 for GMeet/Zoom, 1200 for Teams.
Triggers whenThe host has not admitted the bot before this timeout elapses.
Terminal webhookbot.notallowed (the host never admitted the bot in time).

Use a smaller value for ad-hoc bots so you don’t keep a container alive waiting for an admit that’s never coming. Use a larger value for scheduled bots where the host may take a few minutes to start the meeting.

noone_joined_timeout

What it controlsHow long to stay in an empty meeting waiting for the first other participant to join.
Range60 – 1800. Out-of-range returns HTTP 400.
Default600 for GMeet/Zoom, 1200 for Teams.
Triggers whenThe bot has been admitted, but the participant count besides the bot has been 0 for this many seconds since join.
Terminal webhookbot.stopped (graceful — nobody was coming).

This handles the “host opened the meeting but the participants never showed up” case. It’s distinct from everyone_left_timeout, which only kicks in after a participant joined and then left.

everyone_left_timeout

What it controlsHow long to wait after the meeting goes empty (apart from the bot) before exiting.
Range60 – 1800. Out-of-range returns HTTP 400.
Default600 for GMeet/Zoom, 1200 for Teams.
Triggers whenAt least one other participant joined at some point and the roster has since dropped to bot-only.
Terminal webhookbot.stopped.

If you’re using bot_detection (above), this is your safety net — it still fires if all the notetakers leave too, leaving the bot truly alone. The two cover different shapes of “the meeting is over”: bot_detection for “humans gone, notetakers stayed”, everyone_left_timeout for “everyone gone, including the notetakers”.

voice_inactivity_timeout

What it controlsHow long the bot tolerates total silence in a meeting where participants are present but nobody is speaking.
RangeContainer-enforced. Sensible practical range is 60 – 1800.
Default600 (10 minutes).
Triggers whenThe bot is admitted, the roster is non-empty, but no audio activity has been observed for this many consecutive seconds.
Terminal webhookbot.stopped with the message Bot exited the call: Voice inactivity timeout.
NotesUseful for catching meetings that “ended” without anyone formally leaving — e.g. attendees walked away from their desks but kept their tab open. Independent of bot_detection: voice inactivity fires regardless of who is in the roster.

in_call_recording_timeout

What it controlsAbsolute hard cap on how long a single bot session can stay in the meeting once recording has started.
Range600 – 18000 (10 minutes to 5 hours). Out-of-range returns HTTP 400.
Default14400 (4 hours).
Triggers whenThe cumulative time since the bot started recording exceeds this value.
Terminal webhookbot.stopped.

Treat this as a failsafe, not a knob you tune meeting-by-meeting. It exists so a stuck recording can’t run for days against your credits. Leave the default unless you have a specific reason (e.g. multi-hour all-hands) to extend it.

recording_permission_denied_timeout (Zoom only)

What it controlsHow long to wait for the host to act on Zoom’s “allow recording” prompt before treating it as a denial and leaving.
Range60 – 300. Out-of-range returns HTTP 400.
Default60.
Triggers whenThe bot has joined a Zoom meeting and asked for recording permission, but the host has neither granted nor denied within this window.
Terminal webhookbot.recording_permission_denied → bot.leaving → bot.stopped. See the Bot Lifecycle Webhook Events Guide.
PlatformsZoom only. The field is silently ignored on GMeet and Teams payloads.

Most Zoom hosts respond to the recording prompt within a few seconds; the default of 60 is a good balance. Raise it only if your hosts routinely take a minute or more to acknowledge prompts.


Putting them together

If multiple timeouts would fire at once, whichever expires first wins. In practice that means:

ScenarioWhat ends the meeting
Host never admits the botwaiting_room_timeout → bot.notallowed
Bot admitted, nobody else ever shows upnoone_joined_timeout → bot.stopped
Real meeting happens, everyone (including notetakers) eventually leaveseveryone_left_timeout → bot.stopped
Humans leave, vendor notetakers lingerbot_detection → bot.stopped (typically well before everyone_left_timeout)
Roster non-empty but room is silentvoice_inactivity_timeout → bot.stopped
Multi-hour meeting runs longer than expectedin_call_recording_timeout → bot.stopped (failsafe)
(Zoom only) Host doesn’t respond to the recording promptrecording_permission_denied_timeout → bot.recording_permission_denied → bot.stopped

10) Common pitfalls

”My bot is leaving meetings too early.”

Most likely your matches list is too aggressive for your customer base — e.g. you have "ai" in there and a real participant named "Ainsley Thompson" is being treated as a bot.

  • Substrings match anywhere in the name. Prefer specific brand fragments ("otter.ai", not "ai").
  • Raise activate_after to give real people more time to arrive before the check arms.
  • Raise timeout so a brief network blip on the human’s side doesn’t cause an early exit.

”My bot isn’t leaving even though only notetakers remain.”

Three things to check, in order:

  1. Did you actually configure it? The block is opt-in. Inspect the bot’s stored config via GET /api/v1/bots/{bot_id} — the automatic_leave you see there is what the bot received.
  2. Are the notetakers’ display names in the meeting actually matching? Google Meet sometimes shows "Unknown" for the first ~10 seconds of a join before its name resolver populates a real name. While a participant is unnamed, they don’t match — so the bot can’t conclude “only bots remain”. Wait for names to settle.
  3. Has activate_after elapsed? The check is dormant during that window. If you set activate_after: 1200 and the notetakers leave after only 600 seconds, the check never armed in the first place — everyone_left_timeout takes over instead.

”How do I disable it on a scheduled bot?”

Send a PATCH to /api/v1/calendar/scheduled_bots/{bot_id} with the bot_detection key set to {}, or with automatic_leave set to an automatic_leave object that doesn’t contain the bot_detection block. The next time the bot is launched from that schedule, the feature will be disabled.


11) FAQ

Q: Is this on by default? A: No. It’s strictly opt-in. Bots created without an automatic_leave.bot_detection block behave exactly as before.

Q: Does it cost extra? A: No. There’s no separate charge — you simply stop being billed for the meeting time you would otherwise spend waiting for everyone_left_timeout.

Q: Can I change the keywords after the bot has joined? A: No. The config is locked in at bot creation (or at the most recent PATCH for a scheduled bot before the bot launches). There is no live-update path.

Q: What happens if a human keeps their name as just an emoji or a single letter? A: Their name won’t match any keyword (substring match requires at least one keyword to appear in the name). They will be treated as a human → the bot stays. False-positive-safe.

Q: What if a bot in the meeting uses a non-Latin-script name? A: Substring matching is locale-agnostic — but the keywords you provide must literally appear in the participant’s displayName as the platform reports it. If a notetaker joins with a name in Cyrillic or CJK, add those substrings to matches too.

Q: Does it work for breakout rooms? A: The bot does not currently follow participants into breakout rooms. bot_detection operates on the main-room roster only.

Q: Will the post-call transcript still be generated? A: Yes. A bot_detection exit is treated as a normal graceful leave — the post-call pipeline runs in full (audio upload, transcription, video processing, manifest).

Q: Is the leave event distinguishable from a normal bot.leaving? A: The webhook event name is the same (bot.leaving → bot.stopped), but the underlying status update message includes the substring bot_detection.using_participant_names. If you need to attribute exits, parse the message field on the bot’s status history.


12) Related docs

  • Create Bot Payload Reference — full payload schema, including the automatic_leave.bot_detection block.
  • Bot Lifecycle Webhook Events — the bot.leaving → bot.stopped sequence you’ll observe when this fires.
  • Participant Events — useful for auditing exactly which other participants were in the room at the moment the bot left.
  • Calendar Integration Guide — applying bot_detection to scheduled bots via PATCH.