> ## Documentation Index
> Fetch the complete documentation index at: https://docs.meetingkit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Send bots

> Send the notetaker to a call yourself: from a button for an ad-hoc call, or from your backend when you already know which meetings to record.

A bot is one recording of one meeting. You send one when MeetingKit doesn't learn about
the call from a calendar: your user pastes a link, or your backend already decides which
meetings to record. Either way, the bot belongs to a [meeting](/meetingkit/meetings) and the
results are on that meeting.

## From a button

A "Send bot to meeting" button lets your user paste a link to a call that is on no
calendar.

<Frame>
  <img src="https://mintcdn.com/meeting-kit/DnaA9m78FJYvG19R/images/diagrams/send-bot.svg?fit=max&auto=format&n=DnaA9m78FJYvG19R&q=85&s=8fb1ac71ec4090a37a7081e64494458d" alt="A Send bot to meeting button in the meetings screen opens a form with a meeting link and a Send bot button. Notes: Send bot is POST /bots with member_id and meeting_url; the results come from GET /meetings/{meeting_id}/results." width="680" height="605" data-path="images/diagrams/send-bot.svg" />
</Frame>

```bash theme={null}
curl -X POST "https://api.meetingkit.com/api/v1/bots" \
  -H "Authorization: $MEETINGKIT_API_KEY" \
  -H "Happyscribe-Version: 2026-10-12" \
  -H "Content-Type: application/json" \
  -d '{
    "workspace_id": "wks_01k6a2r9s8x7c2dvq3m5n6p4ab",
    "member_id": "mem_01k69x9s8x7c2dvq3m5n6p4ab",
    "meeting_url": "https://meet.google.com/abc-defg-hij"
  }'
```

```json theme={null}
{
  "id": "bot_01k6b0r9s8x7c2dvq3m5n6p4ab",
  "object": "bot",
  "meeting_id": "mtg_01k6b1r9s8x7c2dvq3m5n6p4ab",
  "meeting_url": "https://meet.google.com/abc-defg-hij",
  "join_at": null,
  "status": "queued",
  "…": "…"
}
```

Without `join_at` the notetaker joins as soon as it can: within seconds, and always within
three minutes. `member_id` makes the meeting this user's, so it shows in their
[meetings list](/meetingkit/meetings) with `source: "bot"`.

`member_id` comes from [connecting a calendar](/meetingkit/calendar-connection). For a user
who never connected one, create their member once and store the `mem_` id:

```bash theme={null}
curl -X POST "https://api.meetingkit.com/api/v1/members" \
  -H "Authorization: $MEETINGKIT_API_KEY" \
  -H "Happyscribe-Version: 2026-10-12" \
  -H "Content-Type: application/json" \
  -d '{ "workspace_id": "wks_01k6a2r9s8x7c2dvq3m5n6p4ab", "email": "ana@acme.com", "name": "Ana García" }'
```

Sending the same email again returns the same member, unchanged: its `name` and `metadata`
stay as they are. Change them with `PATCH /members/{id}`.

## From your backend

If your product already knows which meetings to record, from your own calendar sync or
your users' choices, your backend sends a bot per meeting and keeps it in step with your
calendar.

### Schedule it

Set `join_at` to the meeting's start, as an ISO 8601 time with an offset:

```bash theme={null}
curl -X POST "https://api.meetingkit.com/api/v1/bots" \
  -H "Authorization: $MEETINGKIT_API_KEY" \
  -H "Happyscribe-Version: 2026-10-12" \
  -H "Content-Type: application/json" \
  -d '{
    "workspace_id": "wks_01k6a2r9s8x7c2dvq3m5n6p4ab",
    "member_id": "mem_01k69x9s8x7c2dvq3m5n6p4ab",
    "meeting_url": "https://zoom.us/j/123456789",
    "join_at": "2026-10-13T14:00:00Z",
    "participants": [
      { "name": "Ana García", "email": "ana@acme.com" },
      { "name": "Leo Park", "email": "leo@client.com" }
    ],
    "metadata": { "calendar_event_id": "evt_98741" }
  }'
```

The bot stays `queued` until `join_at`, then joins on time. Send it as soon as the meeting
is on your calendar, and at least ten minutes before it starts: scheduled bots are
guaranteed a seat. `join_at` must be in the future.

* **`participants`** are the people you expect, with names and emails. MeetingKit uses
  them to name the [speakers](/meetingkit/results#what-each-part-gives-you) instead of
  "Speaker 1". Up to 100.
* **`metadata`** holds your own ids: flat string values, merged on update. It comes back on
  the bot and the `bot.*` webhooks. It is not copied to the meeting: set the meeting's own
  with `PATCH /meetings/{id}`.

### One bot per meeting

When several of your users are invited to the same meeting, you want one bot, not one per
attendee. A request with the same `workspace_id`, `meeting_url` and `join_at` as a bot that
is still `queued`, `joining` or `in_call` returns that bot instead of sending a second one.

* Send the meeting's start as `join_at`, the same value for every attendee.
* Send the join link exactly as it appears in the invite.
* It is per workspace: two customers who meet each other get one bot each.

The first request wins: its participants and metadata stay on the bot. This also makes
creation safe to retry: if a `POST` times out, send it again and you get the bot that was
created.

The meeting belongs to the member of that first request. A request naming a different
`member_id` answers `409 bot_already_recording`, with the id of the bot already there: send
the bot once per meeting, with the member who owns it, such as the organizer.

### Keep it in step with your calendar

Store the bot `id` next to your meeting, then:

| When your calendar says… | Do |
| - | - |
| A meeting to record appears | `POST /bots` with `join_at` = start. Store the `id`. |
| It moves, or its link changes | `PATCH /bots/{id}` with the new `join_at` or `meeting_url`. |
| The attendee list changes | `PATCH /bots/{id}` with the full new `participants`. |
| It is cancelled | `DELETE /bots/{id}`. |
| Another attendee has the same meeting | Nothing: it is the same bot. |

`join_at` and `meeting_url` can change until the bot starts joining; `participants` until
the call ends; `metadata` until the bot ends. Past that, `PATCH` answers
`409 bot_not_editable`. Cancelling a bot in the call makes it leave; what it recorded is
kept. Once a day, re-read the next seven days of your calendar and reconcile them with your
bots: calendar notifications get lost.

## How the bot looks and records

A bot follows the workspace's and the member's [recording choices](/meetingkit/meeting-settings#record-my-meetings)
and the workspace's [branding](/meetingkit/setup#branding). Override them for one bot:

| Field | What it sets |
| - | - |
| `bot_name` | The notetaker's name in the call, up to 64 characters. |
| `bot_avatar` | A base64 JPEG up to 256 KB, shown as its video tile. Never returned. |
| `welcome_message` | The chat message it posts when it joins. |
| `recording_mode` | `audio_and_video` or `audio_only`. |
| `recording_start` | `on_join`, or `on_command` to wait until someone in the call asks it to. |
| `silence_alert` | Post a message after a long silence. |
| `language` | The transcription language: a tag such as `es`, `auto` or `multi`: see [Languages](/api-reference/languages). |
| `summary_language` | The summary's language. Defaults to the transcript's. |
| `summary_template` | A built-in template such as `discovery_call`, or a `tpl_` id. |

These values become the meeting's own settings, so the bot and its meeting never disagree.
Before the call, change the language, summary or silence alert with `PATCH /settings/{id}`, the
`settings_id` of the meeting's own assignment in its `settings.assignments`. See
[Settings](/meetingkit/settings).

## What happens next

<Frame>
  <img src="https://mintcdn.com/meeting-kit/DnaA9m78FJYvG19R/images/diagrams/notetaker-lifecycle.svg?fit=max&auto=format&n=DnaA9m78FJYvG19R&q=85&s=0221f187e1c81bb4b7130b03fa46dacf" alt="A bot moves from queued to joining, in_call, processing, and completed. It can also end as failed or cancelled." width="680" height="250" data-path="images/diagrams/notetaker-lifecycle.svg" />
</Frame>

| `status` | Meaning |
| - | - |
| `queued` | Waiting to join, now or at `join_at`. |
| `joining` | Joining the call. Someone may need to admit it. |
| `in_call` | In the call. It records unless it waits for a command (`recording_start: "on_command"`) or the call does not let it record yet. |
| `processing` | The call ended; the recording is being processed. |
| `completed` | Done: the results are on the meeting. |
| `failed` | Ended without a result. `reason` says why: see [Troubleshooting](/meetingkit/troubleshooting). |
| `cancelled` | Cancelled before it finished. |

The `bot.*` webhooks follow these steps. The results are on the meeting: when
`meeting.finished` fires for the bot's `meeting_id`, read
[`GET /meetings/{id}/results`](/meetingkit/results).
