Rate limits
How many API requests each key can make, the send quotas behind them, and what a 429 means.
The API limits requests per API key, by kind of request. Each limit is a bucket: it holds up to burst requests, which can arrive at once, and refills at its rate. A key that keeps under the rate never waits; a short spike up to the burst is fine too.
| Bucket | Covers | Counted per | Rate | Burst |
|---|---|---|---|---|
api.read | Reads: every GET | API key | 50 per second | 100 |
api.write | Writes: every other request, unless a row below covers it | API key | 10 per second | 30 |
api.send | Sends: emails and batches, SMS, inbox messages and replies, broadcasts, Hooks messages and endpoint tests, verifications | API key | 20 per second | 50 |
risk.assess | Risk assessments, and email, IP, phone and content checks | API key | 100 per second | 200 |
risk.limit | Risk rate limit checks (coss.risk.limit()) | API key | 500 per second | 1,000 |
api.authentication.key | Authenticating a key, before any other limit (too_many_authentications) | API key | 50 per second | 100 |
api.authentication.ip | Authenticating any key, before any other limit (too_many_authentications) | client IP | 100 per second | 200 |
api.auth_failure | Requests with an invalid key (too_many_failed_authentications) | client IP | 30 per minute | 30 |
Every key has its own buckets, so a busy production key never slows down another environment's. Requests with an invalid key never reach a route's bucket: they count against the authentication buckets and their client address's allowance of failures, and an address that keeps failing is refused for a while.
Rate limit headers
Responses carry the state of the bucket the request counted against:
| Header | Meaning |
|---|---|
RateLimit-Limit | The bucket's size (its burst) |
RateLimit-Remaining | Requests left in it right now |
RateLimit-Reset | Seconds until it is full again |
When you're limited
A limited request gets a 429 with the usual error envelope
(type: "rate_limit_error") and a Retry-After header with the seconds to wait. Nothing was done:
retry it after that long. The SDK does this for you, waiting
exactly Retry-After (up to 60 seconds) before each of its retries; its POST requests reuse
their Idempotency-Key, so a retried send is never sent twice.
error.code | Why |
|---|---|
rate_limited | The key went over its bucket for this kind of request |
quota_exceeded | The send is over the organization's send quota (below) |
too_many_authentications | Too many requests authenticating, for one key or from one address |
too_many_failed_authentications | Too many requests with invalid keys from your address |
A quota_exceeded without Retry-After can never succeed as it is: the email or batch has more
recipients than the quota allows at once, so split it.
Send quotas
On top of the per-key limits, what an organization sends across all of its keys is limited by its plan: email recipients per second and per day, and SMS segments per second and per day.
| Free | Pro | Scale | Enterprise | |
|---|---|---|---|---|
| Price | Free | $25/month | $250/month | From $2,000/month, annual commitment |
| Included | 3,000 email recipients a month, 3,000 received emails a month, 1,000 form submissions a month, 3 inboxes, 100 risk phone checks a month | $25 of usage, 3 inboxes, 100 risk phone checks a month | $250 of usage, 3 inboxes, 100 risk phone checks a month | Per contract |
| Usage beyond it | Not available | Usage rates below | Usage rates below | Contract rates |
| Sending rate | 2 emails a second, 3,000 a day | 14 emails a second, 100,000 a day | 50 emails a second, 1,000,000 a day | Custom |
| SMS rate | Sandbox only | 10 SMS segments a second, 10,000 a day | 30 SMS segments a second, 100,000 a day | Custom |
| Marketing rate | 2 marketing emails a second | 10 marketing emails a second | 50 marketing emails a second | Custom |
| Domains | 1 | 10 | 100 | Custom |
| Team members | 3 | 10 | Unlimited | Unlimited |
| Webhook endpoints per environment | 2 | 10 | 10 | Custom |
| Contacts per environment | 1,000 | 50,000 | 250,000 | Custom |
| SMS phone numbers | 0 | 5 | 50 | Custom |
| Inboxes | 3 | 1,000 | 10,000 | Custom |
| Email sub-accounts | 0 | 0 | 1,000 | Custom |
| Custom tracking domains | — | Yes | Yes | Yes |
| Audit log | — | Yes | Yes | Yes |
| Log drains | — | — | Yes | Yes |
| Single sign-on (SAML, OIDC) | — | — | Yes | Yes |
| Directory sync (SCIM) | — | — | — | Yes |
| Dedicated IPs | — | — | Add-on | Add-on |
| Email for platforms (sub-accounts) | — | — | Yes | Yes |
- Quotas are charged when a send is accepted (a scheduled send when it is scheduled), per recipient for email and per segment for SMS, and refunded when the send is refused or replayed.
- Marketing email has a slower pace of its own within the email quota, so a broadcast never takes the whole rate from transactional mail.
- Sandbox environments have their own, smaller daily quotas, per environment.
- Sub-accounts can have quotas you set, within the organization's.
- Hooks, Verify and Risk have limits of their own, described with each product.
Higher limits
The per-key buckets (api.read, api.write, api.send, risk.assess, risk.limit) and the send
quotas can be raised for an organization; the authentication buckets can't. Contact coss.com support
with your organization ID and the traffic you expect. Enterprise contracts set their own send rates.