PilotFoxDocs

Rate limits

How many requests an API key may send, how limits are counted and what happens when one is reached.

PilotFox limits requests per credential, so that one integration cannot slow down everyone else. The limits are built into PilotFox; the few that a self-hosted instance can change name their setting.

Budgets per credential

Every endpoint under /api/v1 counts against the budget of the credential that calls it:

RequestsBudget
Reads (GET, HEAD)600 a minute
Writes (POST, PUT, PATCH, DELETE)60 a minute

Reads and writes are separate budgets. Each one is counted in windows of one minute, and every request counts, whatever its answer: a request refused with 400 or 403 uses the budget too.

Who a budget belongs to:

  • an API key: each key has its own budgets, so two keys of one organization do not share them (an organization holds at most 50 active keys);
  • a dashboard user: one budget per person, across their browser tabs;
  • a share link: one budget per link, shared by everyone who opens it;
  • anonymous callers (readers of a public site): one budget per client address, where an IPv6 /64 counts as one address.

Server-side ingest is a write: an ingest key can send 60 requests a minute, each with up to 500 events.

Endpoints with their own budget

A few endpoints have a budget of their own, which they do not share with the general one:

EndpointBudget
POST /api/v1/sites/{siteId}/payments, and the payment refund and delete calls300 a minute per key, for each of the three
POST /api/v1/sites/{siteId}/verify-install10 a minute
GET /api/v1/sites/{siteId}/favicons/{domain}60 a minute
GET /api/v1/affiliate-portal60 a minute per client

GET /api/v1/health is not limited. The realtime stream (GET /api/v1/sites/{siteId}/live/stream) is limited by open connections instead of requests, on each server: at most 5 open streams per credential, 100 per organization by default (LIVE_MAX_STREAMS_PER_ORG on a self-hosted instance), and 20 per person across all organizations by default (LIVE_MAX_STREAMS_PER_OWNER), counting their own streams and those of the API keys they created. Share links and readers of a public site count against the site instead, 50 streams per site by default (LIVE_MAX_PUBLIC_STREAMS_PER_SITE). A stream past a limit is refused with 429 and Retry-After: 5.

The MCP server

The MCP server at /api/mcp allows 120 requests a minute per credential (an API key, or an OAuth token's user and client together) and answers a JSON-RPC error with status 429 past it. The tools it runs call the REST endpoints as the same credential, so each tool call also counts against that credential's REST budget.

When a limit is reached

The request is answered 429 with the standard error envelope, a Retry-After header and the same number of seconds in details.retryAfterSeconds:

HTTP/1.1 429 Too Many Requests
x-ratelimit-limit: 60
x-ratelimit-remaining: 0
x-ratelimit-reset: 59
retry-after: 59
content-type: application/json; charset=utf-8

{"error":{"code":"rate_limited","message":"Too many requests","details":{"retryAfterSeconds":59}}}

Every limited response, successful or not, carries the budget headers:

HeaderMeaning
x-ratelimit-limitThe budget of this kind of request (600 for reads, 60 for writes, or the endpoint's own).
x-ratelimit-remainingRequests left in the current window.
x-ratelimit-resetSeconds until the window ends.

Wait for Retry-After seconds before sending again. A client that keeps an eye on x-ratelimit-remaining can slow down before it runs out.

Refused credentials

Each client address (an IPv6 /64 counts as one) may present 60 refused credentials a minute: unknown, expired or revoked API keys, share tokens and affiliate portal tokens. Past that, for the rest of the minute, every API key or share token it sends is answered 429 without being checked, valid ones included, unless PilotFox verified that key in the last minute. A misconfigured job retrying a revoked key in a loop can therefore lock out the other jobs on the same machine for a minute: stop on 401 instead of retrying.

Heavy queries and exports

Some limits are not counted per minute:

  • Concurrent analytics queries. Each credential runs at most 4 database queries at a time on each server; further queries wait their turn. A query that runs longer than 30 seconds (QUERY_MAX_EXECUTION_SECONDS on a self-hosted instance), or that has not finished 10 seconds after that limit counting its wait, is answered 504. Running many reports in parallel with one key does not make them faster than about four at a time.
  • Exports. One raw-event export runs at a time per organization by default (EXPORT_MAX_CONCURRENT_PER_ORG); a second one is 429 until the first finishes. When the server is running its maximum of exports (2 by default, EXPORT_MAX_CONCURRENT), a new one is 503.
  • Imports. An organization may stream 2 import uploads at a time by default (IMPORT_MAX_UPLOADS_PER_ORG; one more is 429) and hold 3 imports queued or running (IMPORT_MAX_PENDING_PER_ORG; one more is 409).
  • Query size. A report whose result or memory use is too large is a 400 asking you to narrow the window or the filters, and a query string over 16 KB is refused with 431.

Tracking script endpoints

The endpoints the tracking script sends to are not under /api/v1 and are limited per visitor address instead. Events and identify calls (/api/e, /api/i) share a token bucket of 30 requests a second with a burst of 120 by default (INGEST_RATE_PER_IP on a self-hosted instance); the site configuration the script fetches (/api/c/…) has a bucket twice that size, and replay uploads one of their own. An IPv6 /64 counts as one address. A limited request is answered 429. Behind a first-party proxy, each visitor's address keeps its own bucket, counted within that site only, and the proxy's own address has a bucket 100 times larger.

On this page