429 Too Many Requests with a Retry-After header.
Default limits
Each bucket has a limit that applies to a 60-second window. Foil uses the organization defaults unless Foil support has configured an organization-level override.How the window works
Each bucket is a token bucket. It holds up to the limit, so a secret key bucket holds 600 units and a publishable key bucket holds 3,000 units by default, and it starts full. Every request removes its cost from the bucket. The bucket refills continuously at the limit divided by 60 per second, which is 10 units per second for the default secret-key limit and 50 units per second for the default publishable-key limit. A burst that fits within the bucket succeeds. Sustained traffic above the refill rate drains the bucket and gets throttled. When a request arrives and the bucket doesn’t hold enough units, Foil rejects it and still deducts its cost, so retrying beforeRetry-After elapses lengthens the wait. For high-volume integrations - fingerprint sweeps, scoring backfills, bulk audits - contact us to raise the limit for your organization.
Response headers
Every response to a request that passes key verification includes these headers:
Rate-limited responses additionally include:
Foil also limits how quickly a single source can present keys that Foil must verify against its database. A source that exceeds that budget receives
429 with rate_limit.exceeded, Retry-After: 1, and none of the X-RateLimit-* headers.
429 response shape
Rate-limited responses use the standard error envelope:error.retryable is true on every rate_limit.exceeded response. The envelope doesn’t tell you when to retry. The Retry-After header does.
Retry strategy
A correct retry for a 429 does three things: honorRetry-After, add jitter, and cap attempts. The server SDKs don’t retry requests for you, so you implement the loop. The SDK error types also expose the response body but not the response headers. To read Retry-After, call the REST API with your own HTTP client, as the following examples do. If you use an SDK, back off exponentially instead, starting at one second, as described in Errors.
Retry-After. Write a retry loop around every SDK or HTTP call that can receive a 429.
What counts against the limit
Every request that passes key verification consumes units, including requests that go on to fail:- Successful 2xx responses count.
- 4xx client errors from the endpoint, such as a validation failure or a missing resource, count.
- 5xx server errors count.
- 429 responses count, as described in How the window works.
- Requests that Foil rejects while verifying the key don’t count. These include a missing, malformed, or unrecognized key, a key type or mobile client that isn’t allowed on the path, an origin that isn’t allowed, and an inactive organization.
- Preflight
OPTIONSrequests don’t count. - The unauthenticated
GET /v2/collect/pings/:pingIdandPOST /v2/collect/network-checksendpoints don’t count.
POST /v1/agents/ip-rotation costs one unit per item in items, up to 10, so a 10-item batch costs 10 units. Foil reads the item count from the request body before it validates the body. Read X-RateLimit-Cost rather than assuming every HTTP request costs one unit.
In particular, a burst of 422 validation errors during development can eat into your bucket the same way as a burst of successful calls. If your integration is hammering the API in a loop because of a bug, fix the bug rather than raising the limit.
Reducing your request rate
If you’re brushing up against the ceiling, the usual culprits and their fixes:- Per-request session verification. If you’re calling
GET /v1/sessions/:sessionIdon every authenticated API hit, you’re doing too much work. Cache the verified sealed token locally (the token already carries the verdict), and only fall back to the sessions API for audits, escalations, or stale sessions. See API abuse → Session reuse. - Small pages. List endpoints return 50 items per page by default and accept up to 200 with
limit. Requesting the largest page your code can process reduces the number of requests by up to a factor of four. The SDK iterators make one request per page, so they don’t reduce request count on their own. - Unbatched management calls. If you’re provisioning many API keys or organizations, space the calls out with backoff rather than firing them concurrently.
- Polling that could be webhooks. If you poll
GET /v1/sessionsto find new results, subscribe an endpoint to thesession.result.persistedwebhook event instead.
Raising a limit
Production integrations with sustained, legitimate load can have organization-level limits raised above the defaults. Contact support with:- The organization you want raised
- Whether the increase is for publishable keys, secret keys, or both
- Your expected steady-state and peak RPS
- The endpoints you need headroom on
pk_* serves browser and mobile traffic.
What’s next
Errors
The full error envelope, including 429 details.
Pagination
Page size, cursors, and filters.
API abuse
Session reuse patterns that reduce server-side verification rate.
Authentication
Key types, scopes, and lifecycle.