Rate limits
The request budget per app and installation, the stricter per-workspace send limit, the X-RateLimit headers, and how to back off from a 429.
Two budgets apply. A general limit covers every route. A stricter limit covers outbound sends, per workspace. Going over either one returns 429.
Your limits depend on your app
Rate limits are set per app when the app is registered. The numbers on this page are the defaults a new app starts with. Yours may be higher or lower, so read the X-RateLimit-* headers instead of hard-coding these values. Contact support if your integration needs a larger budget.
Standard limits
Applied per app, per installation. Each user who installs your app gets their own budget, and one installation using it up does not affect the others. Every route counts, sends included.
| Default | Window | Scope |
|---|---|---|
| 100 requests | 10 seconds, burst | Per app, per installation |
| 200,000 requests | One UTC day, resets at 00:00 UTC | Per app, per installation |
Once the daily budget is used, every request answers 429 until the reset.
The token endpoints are outside these limits and never answer 429.
Usage headers
Every response that passes authentication carries your current budget. A 401 or 402 is refused before the budget is checked, so it carries none.
| Header | Meaning |
|---|---|
X-RateLimit-Limit-Daily | Your daily allowance. |
X-RateLimit-Daily-Remaining | Requests left today. |
X-RateLimit-Interval-Milliseconds | The burst window length. |
X-RateLimit-Max | Requests allowed per burst window. |
X-RateLimit-Remaining | Requests left in the current burst window. |
A 429 also carries Retry-After with the number of seconds to wait.
Message send limit
POST /v1/messages and POST /v1/messages/scheduled have their own budget on top of the standard limits, on a sliding window per workspace.
| Limit | Window | Scope |
|---|---|---|
| 30 messages | 15 minutes, sliding | Per workspace |
The 31st send inside any 15-minute window is refused with 429. Wait for older sends to leave the window. Attachments are limited to one per message, up to 5 MB.
Check iMessage limit
Apple limits lookups. Each line takes 10 Check iMessage calls a minute. Beyond that the route answers 429 with RATE_LIMITED in the message.
The 429 body
The standard limits, burst or daily:
{
"success": false,
"message": "Daily rate limit exceeded: 200,000 requests per day (resets 00:00 UTC).",
"error": "RateLimitExceeded"
}The send limit:
{
"message": "Too many queued messages for this workspace",
"error": "RateLimitExceeded"
}Both carry "error": "RateLimitExceeded". Check that field, not the message text.
Handling a 429
A 429 means the request was refused, not that it failed. Wait, then retry with exponential backoff and jitter.
async function withBackoff(fn, maxRetries = 5) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fn();
if (res.status !== 429) return res;
const retryAfter = Number(res.headers.get("retry-after")) * 1000;
const delay = retryAfter || Math.min(2 ** attempt * 1000, 60_000) + Math.random() * 1000;
await new Promise((r) => setTimeout(r, delay));
}
throw new Error("Rate limited after retries. Slow the request rate.");
}Bulk sending
Spread sends evenly instead of bursting. A queue that releases one message every 30 seconds per workspace, which is 2 messages a minute, never hits the send limit. See Deliverability for why slow and steady also keeps lines healthy.
Migrating from another provider
What moves through the API, what does not, and the order to cut over safely: contacts, opt-outs, outbound sends and inbound replies.
Errors
The error body, what each HTTP status means, the upper-case codes that appear in the message text, and which failures are safe to retry.

