Nyne.ai API Login
Operations

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.

TierWindowDefault capDerived from
Burst10-second window49 requests / 10sint(perMinute x 10/60 x 3)
Sustained5-minute rolling window500 requests / 5minperMinute x 5
Hourly1-hour window1,000 requests / hourperHour
Burst is the tightest tier
The burst cap is about 3x your per-minute rate spread across a 10-second slice, then floored: at the default 100/min the float math (100 x 10/60 x 3 = 49.9...) truncates down to 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
The quota counts requests; credits meter results. A key can have quota left but be out of credits, or the reverse. See Credits for how usage is billed.

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.

HeaderMeaning
X-RateLimit-LimitRequests allowed in the current window.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetWhen the window resets, as an absolute Unix timestamp.
RateLimit-LimitSame limit, standard-header spelling.
RateLimit-RemainingSame remaining count, standard-header spelling.
RateLimit-ResetSeconds from now until the window resets (relative).
Retry-AfterSeconds to wait before retrying. Present only after you have been throttled.
X-Quota-LimitTotal requests allowed this month.
X-Quota-UsedRequests already spent this month.
X-Quota-RemainingRequests left before the monthly quota is exhausted.
X-Quota-ResetWhen 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.codeHTTPHandlingWhat to do
rate_limit_exceeded429RetryableBack off and retry; honor Retry-After when present.
monthly_limit_exceeded429 / 403Needs actionMonthly quota exhausted. Wait for the reset or raise the quota.
insufficient_credits402Needs actionTop up credits before retrying.
missing_credentials401Needs actionSend the X-API-Key and X-API-Secret headers.
invalid_credentials401Needs actionFix or reissue the key and secret.
subscription_inactive403Needs actionReactivate the subscription behind the key.
ip_not_allowed403Needs actionAllow-list the IP your backend calls from.
access_denied403Needs actionGrant the key access to this product.
service_unavailable503RetryableTransient 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 429 and 503, with exponential backoff (1s, 2s, 4s, ...) and a capped number of attempts.
  • Always honor Retry-After when 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-Remaining and X-Quota-Remaining.
  • Do not retry 402, 401, or 403 - 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
Every poll for an async result is a request, so the same tiers and quota apply while you wait. Space your polls (roughly once a second) and honor 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.