Rate Limits
Understand limits, protect reliability, and plan your integrations.
Phylax API rate limits ensure fair usage and protect the reliability of the platform. Limits are applied per API token and vary by plan and endpoint.
-
Overview
Rate limits are applied over different windows, to balance sustained usage against short bursts.
Per minute Limits the average number of requests you can make each minute. Encourages consistent, steady usage.Burst Allows a short burst of requests above the per-minute limit, for brief spikes.Monthly quota Caps total requests per calendar month. Resets on the 1st of each month, UTC.All three apply at once. A request is rejected by whichever is exhausted first, so staying under the per-minute limit does not help if the monthly quota is spent.
-
Plan limits
Limits are attached to the subscription behind your API token, not to the token itself. Minting a second key does not raise them.
Plan Per minute Burst Allowance Anonymous 30 60 No API access Builder 300 600 Daily Marketplace 900 1,800 Unlimited, priority queue Enterprise 3,000 6,000 Custom volume Anonymous is the only free plan and it has no API access at all. Its limit covers the public catalog, search and public scores on the web, counted per network address rather than per account. Every API and SDK call requires Builder or above.
Under load, Marketplace and Enterprise requests are served ahead of others on a priority queue, so a busy period degrades the cheapest traffic first.
Need higher limits? Contact us to discuss enterprise options.
-
Response headers
Every response carries the current state of your limit, so you never need to guess or discover it by being rejected.
Header Type Description X-RateLimit-Limitinteger Maximum requests allowed in the current window. X-RateLimit-Remaininginteger Requests remaining in the current window. X-RateLimit-Resetinteger Unix time (UTC) at which the current window resets. Retry-Afterinteger, seconds Present on 429responses. Seconds to wait before retrying.HTTP header names are case-insensitive, so
x-ratelimit-remainingandX-RateLimit-Remainingare the same header. Most client libraries normalise them to lowercase; do not compare them with a case-sensitive match. -
Retry strategy
When you receive
429 Too Many Requests, back off and retry. Two rules matter more than the rest: honourRetry-Afterwhen it is present, and add jitter when it is not.const MAX_ATTEMPTS = 5;async function requestWithRetry(url, options = {}) {for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {const res = await fetch(url, options);if (res.status !== 429) return res;// The server knows when it will let you back in. Believe it.const retryAfter = Number(res.headers.get('retry-after'));// Otherwise: exponential backoff with full jitter, capped at 30s.const backoff = Math.min(2 ** attempt, 30);const delay = Number.isFinite(retryAfter) && retryAfter > 0? retryAfter: Math.random() * backoff;await new Promise((r) => setTimeout(r, delay * 1000));}throw new Error('Rate limited after ${MAX_ATTEMPTS} attempts');}import random, time, requestsMAX_ATTEMPTS = 5def request_with_retry(url, **kwargs):for attempt in range(MAX_ATTEMPTS):res = requests.get(url, **kwargs)if res.status_code != 429:return res# The server knows when it will let you back in. Believe it.retry_after = res.headers.get("retry-after")# Otherwise: exponential backoff with full jitter, capped at 30s.backoff = min(2 ** attempt, 30)delay = int(retry_after) if retry_after else random.uniform(0, backoff)time.sleep(delay)raise RuntimeError(f"Rate limited after {MAX_ATTEMPTS} attempts")Terminal window max_attempts=5attempt=0while [ "$attempt" -lt "$max_attempts" ]; doresponse=$(curl -s -w '\n%{http_code}' -H "Authorization: Bearer $PHYLAX_API_TOKEN" \"https://api.phyi.dev/v1/policies/evaluate")status=$(printf '%s' "$response" | tail -n1)[ "$status" != "429" ] && break# Cap at 30s, and jitter so retries do not resynchronise.backoff=$(( 2 ** attempt ))[ "$backoff" -gt 30 ] && backoff=30sleep $(( RANDOM % backoff + 1 ))attempt=$(( attempt + 1 ))doneIf every client doubles its delay on the same schedule, they all retry at the same instant and the next window is exhausted immediately, which produces exactly the synchronised thundering herd the backoff was supposed to prevent. Randomising across the whole interval spreads the retries out. This is why the delay is
random(0, backoff)rather thanbackoff. -
Best practices
Cache results Cache responses for idempotent lookups. A verdict for a pinned version does not change between two calls a minute apart.Batch lookups Use bulk endpoints or batch requests instead of one call per dependency.Respect Retry-After Honour the header rather than guessing, to avoid prolonged throttling.Use scoped tokens Keep a token per service. Limits are per token, so one noisy job cannot starve the rest.
Read the headers instead of counting
Track X-RateLimit-Remaining and slow down as it approaches zero, rather than maintaining your
own counter. Your counter cannot see the other processes sharing that token, and it drifts the
moment a retry, a redirect or a background job makes a request you did not account for.
Related guides
See Error codes for every response status and whether it is retryable, or API reference for per-endpoint limits.