Rate limits
The buckets, the headers, and how a refusal answers.
Every route checks a bucket before it runs. A refusal answers 429 with code rate_limited and a Retry-After header naming how many seconds to wait.
Three headers report the check furthest through its allowance, not the one with fewest requests left, because on a route that keys by contact the subject changes every request. Read X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (a unix timestamp).
The buckets
| Bucket | Window | Keyed on |
|---|---|---|
conversation_write | 60 per minute | member |
conversation_read | 600 per minute | principal |
conversation_bulk | 10 per minute | member |
conversation_create | 10 per minute per contact, 30 per minute per IP | contact and IP |
search | 10 per minute per key, 60 per minute per member | principal |
report_read | 10 per minute per key, 60 per minute per member | principal |
member_read | 600 per minute | member |
member_write | 30 per minute | member |
api_key_write | 30 per minute | member |
messenger_boot | 20 per minute per contact, 120 per minute per IP | contact and IP |
email_inbound | 600 per minute | envelope recipient |
email_domain_verify | 20 per hour | workspace |
A search compiles to a scan, so an API key gets 10 a minute while a signed-in member gets 60 in a bucket of their own. The same split applies to report reads, which scan up to 92 days of rows. Outbound email has its own ceilings: 300 sends an hour per workspace, and 5 an hour to any one address, the loop guard for well-behaved exchanges the header refusals miss.
Note
These are the phase-1 limits, keyed on the member or the principal. Per-key and per-workspace tiers arrive with the public API; until then a key's budget is its owner's.