MeetStream — Deduplication Key & Idempotency Key Guide

View as Markdown

This guide explains the two ways to avoid duplicate bots when calling Create Bot:

MechanismWhere you send itBest for
Idempotency-KeyHTTP headerRetrying the same HTTP request after network errors or timeouts
deduplication_keyJSON request bodyBinding one bot per logical meeting/event in your own system (Recall.ai-style)

Both are optional. If you omit them, every call creates a new bot.

Scope

  • Idempotency-KeyPOST /api/v1/bots/create_bot only
  • deduplication_keyPOST /api/v1/bots/create_bot and POST /calendar/schedule/{event_id}

At a glance

Idempotency-Key (header)deduplication_key (body)
Scoped to(your account, key)(your account, key)
First callHTTP 201 — bot createdHTTP 201 — bot created
ReplayHTTP 507 — treat as successHTTP 200 — looks like a normal create
Key lifetimeBound for the life of the bot recordBound for the life of the bot record
Request body on replayIgnoredIgnored (except meeting_link is checked)
Wrong meeting on replayReturns the original bot silentlyHTTP 409 with an explicit error
Typical key sourceFresh UUID per HTTP attemptYour own ID (calendar event ID, CRM record, etc.)

Rule of thumb: use Idempotency-Key for transport-level retries; use deduplication_key when your app already has a stable ID for “this meeting should have one bot.”

For a deeper dive on Idempotency-Key, see the create_bot API reference, which documents the header and all request fields.


deduplication_key (request body)

Pass a string in the JSON body to say: “for this account, this key should always map to the same bot.”

Example — first create

$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",
> "deduplication_key": "crm-opportunity-88421",
> "video_required": false
> }'

Response (HTTP 201):

1{
2 "bot_id": "bot_a1b2c3d4...",
3 "transcript_id": "t_111...",
4 "meeting_url": "https://meet.google.com/abc-defg-hij",
5 "status": "Active"
6}

Example — replay with the same key and meeting

$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",
> "deduplication_key": "crm-opportunity-88421"
> }'

Response (HTTP 200):

1{
2 "bot_id": "bot_a1b2c3d4...",
3 "transcript_id": null,
4 "meeting_url": "https://meet.google.com/abc-defg-hij",
5 "status": "Active"
6}

Same bot_id as the first call. No new bot. No extra credits.

The replay response is intentionally indistinguishable from a fresh create (aside from transcript_id being null on replay, same as idempotency replays).

Example — same key, different meeting (error)

If you reuse a deduplication_key but change meeting_link, MeetStream returns HTTP 409:

1{
2 "error": "deduplication_key already bound to a different meeting",
3 "existing_bot_id": "bot_a1b2c3d4...",
4 "existing_meeting_url": "https://meet.google.com/abc-defg-hij"
5}

This is deliberate — it surfaces a client bug instead of silently joining the wrong meeting.

Field rules

RuleValue
RequiredNo
Typestring
Length1–2000 characters
CharactersASCII printable only (0x200x7E)
ScopePer MeetStream account (API key owner)

Good key choices: calendar event ID, CRM opportunity ID, internal job ID, user_id + meeting_start_time.

Avoid: timestamps that change on every click, auto-increment integers shared across workers without coordination.

Calendar scheduling

deduplication_key also works on POST /calendar/schedule/{event_id}. Put it in bot_config:

1{
2 "bot_config": {
3 "deduplication_key": "google-cal-event-abc123",
4 "bot_name": "MeetStream"
5 }
6}

Replay behavior matches create-bot: HTTP 200 with the existing scheduled bot, or HTTP 409 if the meeting URL does not match.


Idempotency-Key (HTTP header)

Pass a unique string in the Idempotency-Key header to safely retry the same HTTP request without creating duplicate bots.

Example

$curl -X POST "https://api.meetstream.ai/api/v1/bots/create_bot" \
> -H "Authorization: Token <YOUR_API_KEY>" \
> -H "Content-Type: application/json" \
> -H "Idempotency-Key: 9b1c3f4a-7e2b-4d8f-9a1c-2e6f4b8a0c11" \
> -d '{
> "meeting_link": "https://meet.google.com/abc-defg-hij",
> "video_required": false
> }'
  • First call → HTTP 201
  • Retry with the same header → HTTP 507 (treat as success)

Generate one UUID per logical create attempt, persist it across retries, and accept both 201 and 507 as success.


Using both together

You may send both on the same request. MeetStream checks Idempotency-Key first, then deduplication_key.

Typical pattern:

1{
2 "meeting_link": "https://meet.google.com/abc-defg-hij",
3 "deduplication_key": "crm-opportunity-88421"
4}
1Idempotency-Key: 9b1c3f4a-7e2b-4d8f-9a1c-2e6f4b8a0c11
  • deduplication_key = stable business ID (survives across days and re-schedules)
  • Idempotency-Key = per-HTTP-attempt UUID (survives network retries for that single call)

Which one should I use?

ScenarioRecommendation
HTTP client retry after timeout / 5xxIdempotency-Key
Queue worker redelivery (SQS, Kafka, etc.)Idempotency-Key (persist key with the job)
“One bot per calendar event”deduplication_key set to your event ID
”One bot per CRM record”deduplication_key set to your record ID
Migrating from Recall.aideduplication_key (same semantics)
Maximum safetyBoth — business ID in body, UUID in header

Best practices

DO

  • Generate a fresh UUID v4 per logical createuuid.uuid4() (Python), crypto.randomUUID() (Node), UUID.randomUUID() (Java).
  • Persist the key alongside your job/queue record before the HTTP call, so a retry reuses the same key.
  • Treat 507 (and 200 for deduplication_key) as success — parse the body and proceed as if you just created the bot.
  • Generate the key on the client that owns the retry loop — your worker or the entry point of your background job — so it survives across attempts.

DON’T

  • Don’t reuse a key across different logical creates. Two different meetings → two different keys, or the second call replays the first bot (wrong meeting).
  • Don’t fire parallel retries with the same key. Serialize your retry loop (there is no in-flight race protection).
  • Don’t generate the key inside the retry attempt — a new UUID per attempt defeats the purpose. Generate it once, outside the loop.
  • Don’t rely on the key as an identifier in your own system. Track by the bot_id MeetStream returns.

Reference implementations

Python (requests + tenacity)

1import uuid
2import requests
3from tenacity import retry, stop_after_attempt, wait_exponential
4
5API_URL = "https://api.meetstream.ai/api/v1/bots/create_bot"
6API_KEY = "<YOUR_API_KEY>"
7
8
9def create_bot(meeting_link: str, video_required: bool = False) -> dict:
10 idempotency_key = str(uuid.uuid4()) # once, outside the retry loop
11 headers = {
12 "Authorization": f"Token {API_KEY}",
13 "Content-Type": "application/json",
14 "Idempotency-Key": idempotency_key,
15 }
16 payload = {"meeting_link": meeting_link, "video_required": video_required}
17 return _send_with_retries(headers, payload)
18
19
20@retry(stop=stop_after_attempt(5), wait=wait_exponential(min=1, max=30))
21def _send_with_retries(headers: dict, payload: dict) -> dict:
22 response = requests.post(API_URL, json=payload, headers=headers, timeout=10)
23 if response.status_code in (201, 507): # both are success
24 return response.json()
25 if response.status_code >= 500: # retry only on 5xx
26 response.raise_for_status()
27 raise ValueError(f"Bot creation failed: {response.status_code} {response.text}")

Node.js (axios + axios-retry)

1import axios from "axios";
2import axiosRetry from "axios-retry";
3import { randomUUID } from "crypto";
4
5const API_URL = "https://api.meetstream.ai/api/v1/bots/create_bot";
6const API_KEY = "<YOUR_API_KEY>";
7
8const client = axios.create({ timeout: 10_000 });
9axiosRetry(client, {
10 retries: 5,
11 retryDelay: axiosRetry.exponentialDelay,
12 retryCondition: (err) => err.response?.status >= 500 || !err.response,
13});
14
15export async function createBot(meetingLink, videoRequired = false) {
16 const idempotencyKey = randomUUID(); // once, before the retry loop
17 const response = await client.post(
18 API_URL,
19 { meeting_link: meetingLink, video_required: videoRequired },
20 {
21 headers: {
22 Authorization: `Token ${API_KEY}`,
23 "Content-Type": "application/json",
24 "Idempotency-Key": idempotencyKey,
25 },
26 validateStatus: (status) => status === 201 || status === 507,
27 }
28 );
29 return response.data;
30}

Generate and persist the key when the job is enqueued, not when the worker picks it up — so a redelivered message reuses the same key:

1def enqueue_bot_creation(meeting_link: str, user_event_id: str):
2 job = {
3 "meeting_link": meeting_link,
4 "user_event_id": user_event_id,
5 "idempotency_key": str(uuid.uuid4()),
6 }
7 sqs.send_message(QueueUrl=QUEUE_URL, MessageBody=json.dumps(job))
8
9
10def worker_handler(message: dict):
11 job = json.loads(message["Body"])
12 response = requests.post(
13 API_URL,
14 json={"meeting_link": job["meeting_link"]},
15 headers={"Authorization": f"Token {API_KEY}", "Idempotency-Key": job["idempotency_key"]},
16 )
17 assert response.status_code in (201, 507)

If the worker crashes after the HTTP call but before acking, the queue redelivers the job — and the same key replays the original bot. No duplicate.


Shared guarantees and limits

Both mechanisms share these behaviors:

  1. Per-account isolation — the same key value used by two different MeetStream accounts does not collide.
  2. No parallel-race protection — two simultaneous first-creates with the same key may both succeed. Serialize retries in your client when possible.
  3. Failed creates are not cached — if the original request failed before a bot row was written (validation error, wallet rejection, etc.), a retry executes as a fresh create.
  4. Lookup fails open — on rare internal lookup errors, MeetStream proceeds with creation rather than blocking the request.
  5. No key expiration — keys stay bound for the lifetime of the bot record. Generate a new key for each distinct logical create.

FAQ

Q: Does a replay charge credits? A: No. Only the original successful create charges credits.

Q: Can I look up a bot by deduplication_key or Idempotency-Key later? A: Not via a dedicated API endpoint. Use the bot_id returned in the response.

Q: What HTTP codes should my client treat as success? A:

  • With Idempotency-Key only → 201 and 507
  • With deduplication_key only → 201 and 200
  • With both → 201, 200, and 507

Q: Does deduplication_key work for scheduled bots (join_at)? A: Yes, on POST /api/v1/bots/create_bot with a future join_at, and on calendar schedule endpoints.

Q: What if I omit both? A: Every call creates a new bot, same as before these features existed.