Rate limits and quotas
Two different things can refuse your request for reasons of volume, and they behave nothing alike.
A rate limit is about requests per minute. It answers 429 and it clears
on its own.
A plan quota is about what the account bought — bookings this billing
period, active calendars, workspaces. It answers 402, and it clears when the
period rolls over or the plan changes. Quotas are covered in the
errors guide; the rest of this page is rate limits.
The window is fixed, not sliding
Section titled “The window is fixed, not sliding”This is the fact to design against, and it is the one most likely to be assumed wrong.
The limiter counts requests per calendar minute. The counter for the minute starting at 10:04:00 is reset at 10:05:00, all at once, for everybody. It is not a rolling sixty-second window.
Two consequences fall straight out:
You can spend a whole minute’s budget in the last second of a minute, and another whole budget in the first second of the next. Twenty calls in two seconds is legal. That is a property of fixed windows, not a loophole, and we mention it because backoff written for a sliding window over-corrects here.
Backing off by less than a whole minute may not help. If you are refused at 10:04:30, waiting five seconds gets you refused again. Wait until the next minute boundary.
A refused request still spends budget
Section titled “A refused request still spends budget”A request that is turned away with a 429 has already consumed one unit of
that minute’s budget. The refusal is counted, not waived.
This is deliberate, and it means a client that retries aggressively inside one
minute keeps its own budget at zero. Do not retry a 429 inside the same
minute. You are not just failing; you are paying for each failure.
The budgets
Section titled “The budgets”Rate limits apply to publishable keys. A secret key is not rate-limited.
| Endpoint | Requests per minute |
|---|---|
GET /v1/availability |
60 |
POST /v1/bookings |
10 |
POST /v1/bookings/{id}/verify |
10 |
Each of those is charged twice over: once against the key, and once against the client IP, with the same number. Both counters increment, so a request that clears the key’s budget and then trips the IP budget has still spent the key’s unit. The IP axis exists so that many keys behind one address cannot multiply the effective budget.
The keyless customer routes under /v1/public — the bootstrap call and the
manage link’s cancel and reschedule — get 30 requests per minute per IP,
shared across all of them. They carry no key, so an IP is the only subject
available.
There are no rate-limit headers
Section titled “There are no rate-limit headers”We do not send Retry-After, and we do not send X-RateLimit-Remaining or
anything like it. A 429 is a JSON body and nothing else:
{ "error": { "type": "rate_limited", "message": "Too many requests." }}So you cannot read your remaining budget off a response. Pace against the table above, and treat the next minute boundary as the retry time.
Idempotency is the other half of retrying safely
Section titled “Idempotency is the other half of retrying safely”POST /v1/bookings accepts an Idempotency-Key header. It is the mechanism
that makes a retry safe, and it is worth using on every create rather than only
in your error path.
Send a fresh unique value per booking attempt — a UUID is ideal:
Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7A repeat with the same key and the same body replays the stored response, including its original status code. You get the same booking id back, and no second appointment is created. This is what saves you when a request times out after the booking was actually made.
A repeat with the same key and a different body is refused with a 422
and idempotency_mismatch. The stored response is never replayed in that case:
reusing a key with different content is always a client bug, and replaying would
hide it.
Two details worth knowing:
- Only a
2xxis stored. A failed create is not remembered, so you are free to retry it cleanly with the same key. - Simultaneous requests do not replay. Two identical creates arriving at the
same instant cannot both take the slot, but the loser gets its own
409rather than a copy of the winner’s response. Replay is guaranteed for the sequential case, which is the one a timeout produces.
Retrying, in one paragraph
Section titled “Retrying, in one paragraph”Retry a 429 on the next minute boundary. Retry a 503 — it means two writes
contended and the request is expected to succeed — after a short backoff. Retry
a create with the same Idempotency-Key you used the first time. Do not retry
any other 4xx: the same request will get the same answer.