---
updatedAt: 2026-10-20T22:22:03.000Z
agentTools:
  projectIndex: https://docs.recall.ai/llms.txt
---

# Creating and scheduling bots

Preparing for production: Learn how to schedule bots and ensure your bots join on time; avoid the 507 error

Bots are single-use and each run on their own machine. To make a bot join a meeting, you must have a **meeting URL and a time for the bot to join the meeting**. The most common way to get this information is to:

* Use Recall.ai's <Anchor target="_blank" href="doc:calendar-integration">Calendar Integration</Anchor> to easily connect and automate the bot scheduling lifecycle. If using this integration, you will can use the calendar endpoints defined in the linked guide to schedule bots.
* Use your own calendar integration to get the meeting details from the user's calendar events. You will need to use the [Create Bot](https://docs.recall.ai/reference/bot_create) endpoint to create/schedule bots.

When creating bots, you should schedule the bot as soon as you receive the meeting URL and the meeting start time (e.g. when the user connects their calendar or when you're notified that a calendar event was created).

<Callout icon="✅" theme="okay">
  #### Bots are meant to be scheduled to join calls more than 10 minutes in advance

  <Anchor target="_blank" href="ref:errors#scheduled-bots">Scheduled bots</Anchor> are created when a bot's `join_at` is more than 10 min in advance. Scheduled bots are guaranteed to join on-time and will never be late to meetings.
</Callout>

<Callout icon="❗️" theme="error">
  #### Bots joining calls with less than 10 min notice

  <Anchor target="_blank" href="ref:errors#ad-hoc-bots">Ad-hoc bots</Anchor> are created when a bot's `join_at` is less than 10 min in advance or omitted completely at the time you call the [Create Bot](https://docs.recall.ai/reference/bot_create) endpoint. For example, users may meet spontaneously and the user will supply the meeting URL to join a live (on-demand) meeting.

  Recall maintains a limited pool of warm bots called the [ad-hoc bot pool](https://docs.recall.ai/reference/errors#ad-hoc-bot-pool) (since bots take time to start up), and the pool can deplete during brief spikes in which the API will return [507 errors](https://docs.recall.ai/reference/errors#507-insufficient-storage). We're constantly replenishing the pool so you should be able to claim a bot within a few attempts by retrying every 30s.

  **Ad-hoc bots are a secondary option to scheduled bots** and should be used sparingly, filling in the gaps where scheduled bots can't (e.g. ad-hoc/on-demand meetings). If your use case heavily depends on ad-hoc bots, reach out to `support@recall.ai` with more details about your implementation to discuss potential options available to you.
</Callout>

## How to schedule a bot

To schedule a bot, you only need to set the `join_at` field to a time that is more than 10 minutes in the future. Recall will automatically reserve and start up a machine for your bot to ensure the bot joins on time. You can create a scheduled bot using the example below:

```curl
curl -X POST https://$RECALLAI_REGION.recall.ai/api/v1/bot \
    -H 'Authorization: Token $RECALLAI_API_KEY' \
    -H 'Content-Type: application/json' \
    -d '
    {
      "join_at": "SOME_TIME_MORE_THAN_10_MIN_IN_THE_FUTURE",
      "meeting_url": "$MEETING_URL",
      "bot_name": "My Bot",
    }'
```

### How to update a scheduled bot

Scheduled bots are editable up until they start joining the meeting. The main thing to watch is the **10-minute window** around `join_at`.

<Accordion title="Example: Bot hasn't started joining the call yet (any field except `join_at`)">
  **It’s 2025-12-01 10:00 am UTC.** Bot is scheduled for **2025-12-01 10:01 am UTC**.

  1. Call [Update Scheduled Bot](https://docs.recall.ai/reference/bot_partial_update) to change any fields except `join_at` (e.g. `bot_name`, `meeting_url`, `metadata`, etc.).
</Accordion>

<Accordion title="Example: Bot has started joining or is in the call (cannot update bot)">
  **Bot is joining or in the call.** You need to change something.

  1. You cannot update the bot at this stage.

  If you try to update the bot after the bot has started joining, the API will then return the following error:

  ```
  {
  "code": "update_bot_failed",
  "detail": "Only non-dispatched bots can be updated"
  }
  ```
</Accordion>

<Accordion title="Example: Update a bot's `join_at` to a `join_at` that is > 10 minutes away">
  **It’s 2025-12-01 10:00 am UTC.** Bot is scheduled for **2025-12-01 10:30 am UTC**.

  1. Call [Update Scheduled Bot](https://docs.recall.ai/reference/bot_partial_update) to change any fields (e.g. `bot_name`, `meeting_url`, `join_at`, `metadata`, etc.).
</Accordion>

<Accordion title="Example: Update a bot's `join_at`to a 'join_at' that is <= 10 minutes away)">
  **It’s 2025-12-01 10:00 am UTC.** Bot is scheduled for **2025-12-01 10:30 am UTC**. You want it to join at **2025-12-01 10:07 am UTC**.

  1. Call [Delete Scheduled Bot](https://docs.recall.ai/reference/bot_destroy).
  2. Call [Create Bot](https://docs.recall.ai/reference/bot_create) with `join_at="2025-12-01 10:07"`.

  If you try to update the bot to a time that is less than 10 min away, the API will return the following error:

  ```
  {
  "code": "update_bot_failed",
  "detail": "Not enough time to launch new bot"
  }
  ```
</Accordion>

### How to delete a scheduled bot

Cancelling depends on how close you are to `join_at`.

<Accordion title="Example: cancel bot > 10 minutes before `join_at`">
  **It’s 2025-12-01 10:00 am UTC.** Bot is scheduled for **2025-12-01 10:30 am UTC**. Meeting is canceled.

  1. Call [Delete Scheduled Bot](https://docs.recall.ai/reference/bot_destroy).
</Accordion>

<Accordion title="Example: cancel < 10 minutes before `join_at`">
  **It’s 2025-12-01 10:00 am UTC.** Bot is scheduled for **2025-12-01 10:06 am UTC**. You don’t want it to join.

  1. Call [Remove Bot From Call](https://docs.recall.ai/reference/bot_leave_call_create).
</Accordion>

<Accordion title="Example: Remove the bot from a call after the bot starts joining (or is already in the meeting)">
  **Bot is joining / in the meeting.** You need it to stop.

  1. Call [Remove Bot From Call](https://docs.recall.ai/reference/bot_leave_call_create).
</Accordion>

#### How to delete scheduled bots in bulk

To delete many scheduled bots at once, you will need to:

* Iterate over the results returned by the [List Bots](https://docs.recall.ai/reference/bot_list) endpoint with `join_at >= now()` (you can filter for specific bots if preferred)
* Call the [Delete Scheduled Bot](https://docs.recall.ai/reference/bot_destroy) for every bot in the results

You can also use this method to delete all of your scheduled bots.

<Callout icon="📘" theme="info">
  See this [script](https://github.com/recallai/sample-apps/tree/main/bot_delete_scheduled_bots) to filter and unschedule/delete many scheduled bots in bulk.
</Callout>

### How to list all scheduled bots

You can retrieve all bots scheduled in the future by calling the [List Bots](https://docs.recall.ai/reference/bot_list) endpoint with the `join_at_after=CURRENT_ISO8601_TIMESTAMP` filter.

```curl
curl --request GET \
     --url 'https://REGION.recall.ai/api/v1/bot/?join_at_after=CURRENT_ISO8601_TIMESTAMP' \
     --header 'Authorization: API_KEY' \
     --header 'accept: application/json'
```

### Scheduling bots with your own calendar integration

If you already integrate with Google Calendar / Outlook outside of Recall.ai's <Anchor target="_blank" href="doc:calendar-integration">Calendar Integration</Anchor>, to sync bots to a calendar's events, you should:

* Run a daily full sync of all events in the next 7 days to seed and reconcile your upcoming window.
* Use webhooks / change notifications to upsert/delete events inside that window and adjust bot scheduling when times or URLs change.

A daily sync ensures a calendar's “upcoming” calendar event window stays complete as time moves forward. Meetings created weeks or months in advance will naturally enter the next-7-days window over time, and the daily pass is what brings them into scope even if nothing about the event changes.

<br />

## How to deduplicate bots

<Callout icon="❗️" theme="error">
  Deduplication is only supported with the calendar integration.

  If you schedule bots directly via the [Create Bot](https://docs.recall.ai/reference/bot_create) endpoint, Recall will not deduplicate bots automatically. To avoid creating multiple bots for the same meeting, you must implement deduplication in your own app (typically by persisting a meeting → bot mapping in your database). See below for details on [how to manually deduplicate bots](https://docs.recall.ai/docs/creating-and-scheduling-bots#manually-deduplicating-bots).
</Callout>

Deduplication allows you to schedule one bot to join a meeting on behalf of multiple participants. This is useful in cases where multiple users have a shared event marked for recording and you want to avoid scheduling multiple bots.

The <Anchor target="_blank" href="doc:calendar-integration">Calendar Integration</Anchor> supports bot deduplication by default:

* <Anchor target="_blank" href="doc:calendar-v1-1">Calendar V1</Anchor> - only one bot joins the call among all of your calendar v1 users
* <Anchor target="_blank" href="doc:v2">Calendar V2</Anchor> - you can configure how bots are deduplicated by using <Anchor target="_blank" href="doc:scheduling-guide#4-deduplicating-bots-custom-bot-deduplication-key">deduplication keys</Anchor>

For bots created outside of our calendar integration using the [Create Bot](https://docs.recall.ai/reference/bot_create) endpoint, Recall does not provide built-in bot deduplication and you will need to manually implement the logic to deduplicate bots in your app.

### Manually deduplicating bots

#### Defining a deduplication key

A **deduplication key** is a stable key that identifies **the single bot for a given meeting instance for the scope you choose** (per meeting, per company, or per participant).

##### Common deduplication patterns

<Accordion title="Example: One bot joins per meeting (Recommended)">
  You can ensure only one bot joins per meeting by creating a deduplication key in the following format:
  `{meeting_start_time}-{meeting_url}`. This deduplication key will ensure only one bot is scheduled across all connected calendars(for your Recall account).
</Accordion>

<Accordion title="Example: One bot joins per company per meeting">
  You can ensure only one bot joins per company by creating a deduplication key in the following format: `{meeting_start_time}-{meeting_url}-{calendar_email_domain}`. This deduplication key will ensure one bot per unique company domain across all connected calendars(for your Recall account) is scheduled.
</Accordion>

<Accordion title="Example: Every participant gets a bot (No deduplication)">
  You can ensure only one bot joins per company by creating a deduplication key in the following format: `{meeting_start_time}-{meeting_url}-{event_or_unique_id}`. This deduplication key will ensure one bot will join per participant.
</Accordion>

> Note: meeting URLs can be reused (e.g., recurring meetings), which is why most keys include some notion of time and/or a company/user scope.

<Callout icon="💡" theme="info">
  #### Deduplicating ad-hoc bots for meetings with scheduled bots

  In practice, ad-hoc bots rarely join calls alongside scheduled bots because they serve different purposes; most bots are scheduled in advance, while ad-hoc bots are typically deployed when scheduled bots cannot be arranged in time

  That said, if you'd like to deduplicate ad-hoc bots too, developers have used some form of `{meeting_url}-{approximate_start_time}` as the deduplication key, where `approximate_start_time` could be the nearest half hour (or similar, depending on how your users typically conduct these ad-hoc meetings).
</Callout>

#### Implementing bot deduplication in your app

Once you’ve defined a `dedup_key`, treat it as your app’s identifier for **the single bot to use for that meeting instance within the scope you chose** (per meeting, per company, or per participant). In implementation, your backend stores a mapping of `dedup_key → bot_id`, meaning:

* The `dedup_key` is how you identify “this meeting instance + scope” in your app.
* The `bot_id` is the Recall bot you created the first time you saw that `dedup_key`, and it’s the bot you will use for every subsequent request for the same `dedup_key`.

A typical flow looks like this:

1. Generate the `dedup_key` for the meeting instance based on the scope you chose.
2. Check whether your app has already created a `bot_id` for that `dedup_key`.
3. If a `bot_id` exists, you can reference this `bot_id` so the same bot is used for this meeting instance.
4. If no `bot_id` exists yet:
   * **Create a new bot** for the meeting via the [Create Bot](https://docs.recall.ai/reference/bot_create) endpoint, then
   * **Store the returned&#x20;**`bot_id` under that `dedup_key` so future requests map to the same bot.

<Callout icon="📘" theme="info">
  **Associate&#x20;**`bot_id`**&#x20;to users in your app**

  Many products have multiple users who should be able to view the same meeting outputs (transcript, recordings, metadata). To support this, store a relationship in _your_ database between the requesting user(s) and the `bot_id` you returned (for example, a `user_id ↔ bot_id` table).

  This association is not required for deduplication itself - it’s only needed if multiple users in your product should be able to access the same bot’s
</Callout>

<Accordion title="Example: One bot per meeting — first request (no existing bot for this dedup_key)" icon="fa-info-circle">
  In this example, your scope is **one bot per meeting instance**

  The user wants to record a specific meeting on their calendar so for a given calendar event:

  * You choose `dedup_key = {meeting_start_time}-{meeting_url}` from the calendar event details.
  * Your backend generates the `dedup_key` and checks your DB for `{dedup_key → bot_id}`.
  * No mapping exists yet, so your backend creates a new bot via **Create Bot** endpoint.
  * Recall returns `bot_id = bot_123`.
  * Your backend stores `{dedup_key → bot_123}` so the next request for the same meeting instance returns the same bot.
</Accordion>

<Accordion title="Example: One bot per meeting — subsequent request (existing bot for this dedup_key)" icon="fa-info-circle">
  In this example, your scope is **one bot per meeting instance**. This is what happens when another request comes in for the **same meeting instance** and you’re deduping **per meeting**.

  The user wants to record a specific meeting on their calendar so for a given calendar event:

  * Your backend generates the same `dedup_key = {meeting_start_time}-{meeting_url}`.
  * Your backend finds `{dedup_key → bot_123}` in your DB.
  * Your backend returns `bot_123` and does **not** create another bot.
  * (Optional) Your backend associates User B with `bot_123` so both users can access the same outputs in your product.
</Accordion>

<Accordion title="Example: One bot per company per meeting — two companies on the same meeting" icon="fa-info-circle">
  In this example, your scope is **one bot per company per meeting instance**. This means the same meeting can intentionally have multiple bots — one per company.

  User A from `ACME.com` wants to record a specific meeting on their calendar so for a given calendar event:

  * You choose `dedup_key = {meeting_start_time}-{meeting_url}-{calendar_email_domain}` for the participant.
    * Your backend generates `...-acme.com`, finds no mapping, creates a bot, and stores `{dedup_key → bot_acme_123}`.

  User B from `acme.com` wants to record the same meeting instance.

  * Your backend generates the same deduplication key as above, finds `{dedup_key → bot_acme_123}`, and returns `bot_acme_123` (no new bot is created).

  User C from `globex.com` clicks “Record” for the same meeting instance.

  * Your backend generates a different key `...-globex.com`, finds no mapping, creates a second bot, and stores `{dedup_key → bot_globex_456}`.

  Result: the meeting instance can have **two bots** (one for `acme.com`, one for `globex.com`) because that’s the scope you chose.
</Accordion>

<Callout icon="⚠️" theme="warning">
  **Tracking changes to the&#x20;**`dedup_key`**&#x20;inputs**

  To keep your `dedup_key → bot_id` mapping accurate over time (e.g., if a meeting is rescheduled, the meeting URL changes, or your dedupe scope changes), you need a stable way to locate the existing mapping when the meeting details change.

  In practice, store a link between the record in your system that represents the meeting (e.g., a calendar event ID or an internal meeting ID) and either:

  * the current `dedup_key`, or
  * the inputs you used to build it (`meeting_url`, `meeting_start_time`, scope fields).

  When that meeting record changes, recompute the `dedup_key` and update your stored mapping so future requests resolve to the correct `bot_id`.
</Callout>

<br />

# FAQ

## What's the concurrent bot limit?

The "concurrent bot limit" refers to concurrent bots live in meetings deployed ad-hoc (referred to as <Anchor target="_blank" href="ref:errors#ad-hoc-bots">ad-hoc bots</Anchor>). Ad-hoc bots are rare in practice because most bots should be scheduled in advance. Ad-hoc bots are only meant to fill in the gaps where <Anchor target="_blank" href="ref:errors#scheduled-bots">scheduled bots</Anchor> (bots scheduled more than 10 minutes in advance) can’t (e.g., last-minute meetings).

**There are no limits on concurrent bots live in meetings scheduled more than 10 minutes in advance**. This effectively means that most bots won’t count toward this 30 concurrent bots limit

If you are expecting high ad-hoc bot usage, please reach out to support with details around your constraint/limitations and we can work with you to find a oslution that fits your use case.

## How do I handle scheduling bots for back-to-back meetings with the same meeting URL?

If you can't use a unique meeting session for each back-to-back meeting, you will need to schedule a separate bot for each appointment and keep the meeting running between appointments. You can admit from the waiting room and remove bots from the call as needed - for example after a call has ended, remove the bot that's currently in the call and then let the next bot in when you’re ready to begin.

If you end the meeting and the bots leave, you’ll need to deploy new bots when you restart it. Bots are single-use, so once they leave a meeting, they cannot rejoin - even if the meeting URL stays the same.

<br />

<br />