Skip to content

Idempotency and retries

A network can lose the answer to a request that worked. Sending it again must not make a second meeting or pay for a second Ask. An Idempotency-Key header makes the second try safe.

The three routes that spend minutes take an Idempotency-Key header:

Route Scope A replay answers
POST /recordings recordings:write 202 with the same meeting
POST /recordings/{id}/ask recordings:query 200 with the same answer
POST /series/{id}/ask series:query 200 with the same answer
  • A key is 8 to 64 characters, counted after spaces at either end are trimmed. Anything else answers 400 VALIDATION.
  • It is optional, and worth sending on every request to these routes: without one, Lobbe can’t tell a retry from a new request.
  • On the two Asks, the key can also go in the body as idempotencyKey. When both are sent, the header wins.
  • The other routes spend nothing, so they take no key.
Terminal window
key=$(uuidgen) # one key for this meeting, kept for every retry of it
curl https://app.lobbe.ai/api/v1/recordings \
-H "Authorization: Bearer $LOBBE_TOKEN" \
-H "Idempotency-Key: $key" \
-F "audio=@meeting.m4a;type=audio/mp4"

Send a request again with the same key, and Lobbe doesn’t run it again: it answers with what the first one made.

  • A meeting: 202 with the same meeting, its id and its status now. There is no second meeting and no second charge, and the token’s daily cap counts it once.
  • An Ask: 200 with the first answer, and nothing is charged.
  • A replay is answered even when a limit is reached: Lobbe’s daily limits, or the token’s daily cap.
  • Lobbe matches on the key alone. The same key with another file, or another question, still gets the first meeting or the first answer back, and the new one is never sent. A new request needs a new key.
  • Only a request that worked is replayed. One that answered an error stored nothing, so sending it again with the same key runs it for real. That is what you want after a 5xx, or after a 402 once the owner has bought minutes.
  • 503 UPLOAD_IN_PROGRESS means the first request with this key is still storing its audio. Wait the seconds Retry-After gives and send the same request again: it then answers with that meeting.

A key is the owner’s, and doesn’t expire

Section titled “A key is the owner’s, and doesn’t expire”
  • A key doesn’t expire after 24 hours, or a week. A meeting’s key stays with the meeting it made for the life of that meeting, even after it is deleted. An Ask’s key stays with the Asks of that meeting or series.
  • A meeting’s key is the account’s. One key makes one meeting, across the app and every token of the account. An Ask’s key belongs to one meeting or one series, so another meeting can use the same key.
  • Each token replays only its own requests. A key that another token of the account, or the app, already used answers 409 IDEMPOTENCY_KEY_IN_USE. The answer names no meeting and no answer: the earlier one never reaches this token.

So send a random UUID for each new request. Never make the key from the file, a hash of the audio for example: two agents of one account that hash the same file would collide, and the second would get 409 IDEMPOTENCY_KEY_IN_USE instead of its meeting.

  • Make the key once, before the first try, and keep it for every retry of that request. If your tool can restart, store the key with the job.
  • Network errors and 5xx: send the same request again with the same key, waiting longer each time (2 seconds, then 4, 8 and so on, up to a limit you choose). If the first try had in fact worked, the retry gets its outcome.
  • 429: wait the seconds Retry-After gives, then send the same request with the same key. For a daily limit, that can be hours. A 429 without Retry-After won’t pass by waiting: that is a TOKEN_DAILY_CAP this request alone is bigger than, or Lobbe’s own DAILY_LIMIT_REACHED on a meeting. Stop, and tell the person.
  • An Ask that fails (502 GENERATE_FAILED) isn’t charged, but it can count toward Lobbe’s daily limit on Asks: wait before trying again, and don’t loop on it.
  • Other 4xx: the request as it is won’t work. Change it as its code says: see Errors. 409 IDEMPOTENCY_KEY_IN_USE needs a new key.

A submit that does all of this (it needs uuidgen; any random UUID will do):

Terminal window
key=$(uuidgen)
delay=2
for attempt in 1 2 3 4 5 6; do
rm -f headers.txt body.json
code=$(curl -sS -o body.json -D headers.txt -w '%{http_code}' \
https://app.lobbe.ai/api/v1/recordings \
-H "Authorization: Bearer $LOBBE_TOKEN" \
-H "Idempotency-Key: $key" \
-F "audio=@meeting.m4a;type=audio/mp4") || code=000
wait=$(awk -F': *' 'tolower($1) == "retry-after" { print $2 + 0 }' headers.txt 2>/dev/null)
case "$code" in
202) cat body.json; break ;;
429) [ -n "$wait" ] || { cat body.json; break; }; sleep "$wait" ;;
5?? | 000) sleep "${wait:-$delay}"; delay=$((delay * 2)) ;;
*) cat body.json; break ;;
esac
[ "$attempt" = 6 ] && { echo "gave up after 6 tries"; cat body.json 2>/dev/null; }
done

000 is a network error: curl got no answer. The loop sends the same key every time, so however many tries it takes, there is one meeting.