The limit
120 requests per minute, per account, per surface. The buckets are keyed to the clinic account, not to individual credentials — but the REST API and the MCP server each have their own: every API key on the account shares one 120/minute REST budget, and every AI connector shares a separate 120/minute MCP budget. A runaway batch job can’t starve the team’s assistants, and a looping assistant can’t starve your integrations — but a bursty integration still spends its whole surface’s allowance for the account, so pace accordingly. The window is a plain per-minute bucket, not a per-second pace: bursts are fine, up to 120 requests in quick succession (unlike the legacy API’s strict one-request-per-second throttle) — the budget just has to last the rest of the minute. An unauthenticated request (no bearer token) is limited far more tightly — 2 requests per minute, per IP address — so anonymous traffic can’t starve the account’s legitimate integrations or probe the API at volume. Legitimate unauthenticated traffic is essentially one thing: the MCP handshake’s initial 401 that points a connecting client at OAuth discovery, and that fits comfortably. Every response carries:When you go over
A429 with a stable error shape:
Retry-After is the number of seconds until the bucket has room again — wait at least that long before your next call rather than retrying immediately. See Errors for rate_limited alongside the rest of the error catalogue.
Dynamic client registration for MCP connectors (
POST /v3/oauth2/register) has its own, much tighter limits — 10 requests per minute per account and IP, and 60 per minute per account — since it’s unauthenticated by design (there’s nothing to pre-register). It doesn’t share the 120/minute budget; ordinary API and MCP calls are unaffected by it.Pacing an integration
- 120/minute is two requests per second sustained — comfortable for interactive use (a booking widget, a CRM sync reacting to webhooks), but remember it’s the whole account’s budget, shared with every other integration and connector on it.
- For bulk work — imports, full exports — pace deliberately rather than bursting. Run steadily through the minute instead of firing 120 requests in the first seconds and idling for the rest; watch
X-RateLimit-Remainingand slow down as it approaches zero. See Data migration → Throughput and retries for the numbers on a realistic import. - Batch where the API lets you. A patient with embedded
numbers[]/addressis one request; an invoice with all its line items is one request;POST /line_itemswithlines: [...]adds several lines in one call. Fewer, larger requests get more done inside the same budget than the equivalent one-record-at-a-time calls. - Prefer cursor pagination for large reads. It doesn’t change your rate budget, but it keeps each page’s cost constant, so a long walk doesn’t get slower (and doesn’t tempt you into re-fetching pages) as a dataset grows.
- Back off on
429, don’t retry immediately. A tight retry loop against an exhausted bucket just keeps failing until the window rolls over — honourRetry-After.
Idempotency-Key interplay
AnIdempotency-Key retry and a fresh request cost the same rate-limit token — replaying an idempotent request still counts against the account’s 120/minute, because the rate limiter runs before the idempotency check in the request pipeline. Idempotency keys make retries safe (no duplicate record), not free. If a client is retrying the same key in a tight loop because of a slow response, it will still get throttled like any other burst; the fix is the same as for any timeout — back off, don’t hammer. See Writing → Retrying safely for how idempotency keys behave on their own terms.