Rate limits, quotas & error handling
Every API key is throttled by a three-tier rate limiter and a monthly quota. When you push past either, the API answers with a structured error and headers that tell you exactly how long to wait. This page covers the tiers and their default caps (100/min, 1,000/hour), the headers to read, the error taxonomy, and how to back off so a burst never turns into a cascade of failures.
The three-tier rate limiter
Requests are counted per API key across three windows at once. All three are enforced together,
and the first tier you exceed wins - so a fast burst can trip the burst tier even while you are
well under the hourly cap. The caps scale from the key's rate_limit_per_minute and rate_limit_per_hour, so your real limits are 100/min, 1,000/hour.
| Tier | Window | Default cap | Derived from |
|---|---|---|---|
| Burst | 10-second window | 49 requests / 10s | int(perMinute x 10/60 x 3) |
| Sustained | 5-minute rolling window | 500 requests / 5min | perMinute x 5 |
| Hourly | 1-hour window | 1,000 requests / hour | perHour |
Burst is the tightest tier
49, not 50. If you fan out
many requests in parallel, pace them: hitting the burst tier returns 429 even when your minute and
hour budgets are untouched.The monthly quota
On top of the per-second and per-hour rate limits, each key has a monthly request quota - a
ceiling on how many requests it can make in a billing month. The rate limiter smooths traffic over short
windows; the quota caps total volume over the month. You can exhaust the quota without ever tripping a rate tier,
and when you do, requests fail with monthly_limit_exceeded until the quota resets or you raise it. Watch
the X-Quota-* headers below to see how much of the month's budget is left.
Quota is not credits
Response headers to read
Every response carries the live rate-limit and quota counters. Read them proactively - warn or slow down as Remaining approaches zero, rather than waiting for the first 429. Note the two Reset conventions:
the X-RateLimit-Reset value is an absolute Unix timestamp,
while RateLimit-Reset is relative seconds from now.
| Header | Meaning |
|---|---|
| X-RateLimit-Limit | Requests allowed in the current window. |
| X-RateLimit-Remaining | Requests left in the current window. |
| X-RateLimit-Reset | When the window resets, as an absolute Unix timestamp. |
| RateLimit-Limit | Same limit, standard-header spelling. |
| RateLimit-Remaining | Same remaining count, standard-header spelling. |
| RateLimit-Reset | Seconds from now until the window resets (relative). |
| Retry-After | Seconds to wait before retrying. Present only after you have been throttled. |
| X-Quota-Limit | Total requests allowed this month. |
| X-Quota-Used | Requests already spent this month. |
| X-Quota-Remaining | Requests left before the monthly quota is exhausted. |
| X-Quota-Reset | When the monthly quota rolls over. |
Always honor Retry-After
Retry-After appears only when you have been throttled, and it is the server telling you exactly how
many seconds to wait. When it is present, use it verbatim - it beats any backoff you compute yourself.Error taxonomy
Throttling and access failures use the same uniform envelope as every other reply: success is false, the detail moves under error, and a timestamp is always present. Branch on the stable error.code, never the human-readable message:
{
"success": false,
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Retry after the period in the Retry-After header."
},
"timestamp": "2026-05-28T18:04:45"
}Each error.code maps to an HTTP status, and tells you whether to
retry or to fix something first:
| error.code | HTTP | Handling | What to do |
|---|---|---|---|
| rate_limit_exceeded | 429 | Retryable | Back off and retry; honor Retry-After when present. |
| monthly_limit_exceeded | 429 / 403 | Needs action | Monthly quota exhausted. Wait for the reset or raise the quota. |
| insufficient_credits | 402 | Needs action | Top up credits before retrying. |
| missing_credentials | 401 | Needs action | Send the X-API-Key and X-API-Secret headers. |
| invalid_credentials | 401 | Needs action | Fix or reissue the key and secret. |
| subscription_inactive | 403 | Needs action | Reactivate the subscription behind the key. |
| ip_not_allowed | 403 | Needs action | Allow-list the IP your backend calls from. |
| access_denied | 403 | Needs action | Grant the key access to this product. |
| service_unavailable | 503 | Retryable | Transient outage. Back off and retry the same request. |
Only 429 and 503 are safe to retry unchanged. Note that monthly_limit_exceeded can surface as 429 or, on some paths, 403 - either way it means the quota is spent, not that a short wait
will help. For the credentials and access codes
(401/403) see the Authentication guide; for insufficient_credits see Credits.
Recommended client behavior
Build one retry policy and apply it everywhere:
- Retry only
429and503, with exponential backoff (1s, 2s, 4s, ...) and a capped number of attempts. - Always honor
Retry-Afterwhen it is present - use its value instead of your computed delay. - Surface the quota headers: warn before you hit the wall rather than after, using
X-RateLimit-RemainingandX-Quota-Remaining. - Do not retry
402,401, or403- they need an action (credits, credentials, or access), not another call.
# Retry 429/503 with backoff; honor Retry-After when the server sends it. attempt=0 while :; do code=$(curl -sS -D headers.txt -o body.json -w '%{http_code}' \ "https://api.nyne.ai/person/enrichment?request_id=$RID" \ -H "X-API-Key: $KEY" -H "X-API-Secret: $SECRET") case "$code" in 2*) break ;; # done 429|503) retry=$(grep -i '^Retry-After:' headers.txt | tr -d '\r' | awk '{print $2}') # Prefer Retry-After; otherwise exponential backoff (1s, 2s, 4s, ...). sleep "${retry:-$(( 2 ** attempt ))}" attempt=$(( attempt + 1 )) ;; 401|402|403) echo "not retryable - fix creds/credits/access"; break ;; *) echo "unexpected $code"; break ;; esac done
Polling counts against these limits
Retry-After there too. See Async requests for the
submit-and-poll pattern.What's next
- Credits - how usage is metered and when a request consumes a credit.
- Authentication - keys, secrets, IP-locking, and the auth error codes.
- Async requests - the submit-and-poll lifecycle that must respect these limits.
- Responses - the uniform envelope every reply shares.