API rate limiting
How /api/v1 throttles traffic, which response headers to read, and how that relates to monthly API quotas.
Loading article…
The public API applies two separate limits:
This guide explains per-minute limits and response headers. Monthly quotas are summarized at the end.
When you exceed a per-minute limit, the API responds with 429 and a JSON body:
{
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded, please try again in 42s"
}The 429 status code is defined for this purpose in RFC 6585 and described in RFC 9110 (HTTP Semantics) as the appropriate response when the client has sent too many requests in a given period.
On 429 responses from rate limiting, we send a Retry-After header whose value is a delay in seconds (not an HTTP-date). That matches the delay-in-seconds interpretation documented for Retry-After in RFC 9110, Section 10.2.3.
Retry-After: 42Wait at least that many seconds before retrying. For automated clients, combine this with exponential backoff and jitter.
Authenticated /api/v1 responses include:
| Header | When present | Meaning |
|---|---|---|
X-RateLimit-Limit | Success and throttle | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Same | Requests left in the window (0 when exhausted) |
X-RateLimit-Reset | Same | Seconds until the window resets, not a Unix timestamp |
Retry-After | Only on 429 | Same value as X-RateLimit-Reset for that response |
These names follow the widespread X-RateLimit-* pattern used by many HTTP APIs. They are not defined by an IETF RFC. The IETF is standardizing similar fields (for example RateLimit-Limit) in draft-ietf-httpapi-ratelimit-headers; our headers use the same ideas (limit, remaining, reset interval) with the familiar X- prefix.
Read header names case-insensitively per RFC 9110. In browsers, exposed header names may appear with different casing.
Per-minute limits use a fixed window (typically 60 seconds from your first request in that window):
This is not a sliding window. Traffic at the end of one minute and the start of the next can briefly exceed the per-minute number across a two-minute span.
| Request type | Limit applies to |
|---|---|
/api/v1/team/... with an API key | Each API key has its own counter |
GET /api/v1/openapi.json (no API key) | Client IP address |
Keys on the same team do not share one per-minute counter. Use one key per integration; do not rotate keys to get around limits.
| Endpoint type | Limit | Window |
|---|---|---|
Authenticated team API (/api/v1/team/...) | 60 requests | 1 minute |
OpenAPI document (GET /api/v1/openapi.json) | 30 requests | 1 minute (per IP) |
Tool result polling (GET /api/v1/tools/.../results) | 300 requests | 1 minute (per API key), when your deployment exposes tool routes |
Starting an async tool job (POST /api/v1/tools/...) counts toward the same 60/min limit as other authenticated team API calls. Polling results uses the higher limit so you can poll every few seconds without exhausting the general cap.
curl -sS -D - -o /dev/null \
-H "Authorization: Bearer YOUR_KEY" \
"https://YOUR_DOMAIN/api/v1/team"A successful response:
HTTP/2 200
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 58After you are throttled:
HTTP/2 429
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 37
Retry-After: 37
Content-Type: application/json
{"code":"RATE_LIMIT_EXCEEDED","message":"Rate limit exceeded, please try again in 37s"}On 429 with code RATE_LIMIT_EXCEEDED:
wait at least Retry-After seconds (or use X-RateLimit-Reset)
retry with backoff and jitter; cap retry attempts
On 429 with code API_QUOTA_EXCEEDED:
stop tight-looping; check plan usage or wait for the billing period to resetTrack X-RateLimit-Remaining on success to slow down before you hit 429. For tool jobs, poll results on an interval of a few seconds rather than hammering the API.
If you use the TypeScript SDK (emailguard-sdk), honor Retry-After in your retry logic when you handle PublicApiError from throttled responses.
On plans with API usage metering, each successful /api/v1 call may count toward your team’s monthly allowance. Limits depend on your subscription tier.
| Per-minute rate limit | Monthly quota | |
|---|---|---|
| Scope | Per API key (or per IP for unauthenticated routes) | Per team |
| HTTP status | 429 | 429 |
| Error code | RATE_LIMIT_EXCEEDED | API_QUOTA_EXCEEDED |
| Response headers | X-RateLimit-*, Retry-After | No dedicated quota headers |
| What to do | Wait for the minute window to reset | Upgrade your plan or wait for the billing period to reset |
When the monthly quota is exceeded, the API returns API_QUOTA_EXCEEDED and does not include X-RateLimit-* headers on that response.
Per-minute throttles (RATE_LIMIT_EXCEEDED) do not count against your monthly quota.
View current usage with GET /api/v1/team/billing/usage when your API key includes the billing:read scope.