Skip to main content

Budgets

Budgets are counted per API key and per request class, over a one-minute sliding window. All keys for the same user also share a cap equal to 2× a single key’s budget.
  • “Concurrent” counts in-flight HTTP requests, not active runs. POST /v1/runs responds in milliseconds, so this limit rarely matters. POST /v1/go/run (synchronous) holds a slot for the entire search. Active runs have a separate cap: see Runs per organization.
  • Apart from these limits, at most 5 Go jobs (POST /v1/go/runs) can be running at once per user: a sixth returns 429 too_many_runs with Retry-After: 30.
  • Failed authentication is limited per IP address. See Failed authentication.

Failed authentication

Each IP address has a budget of 100 failed authentications per 10-second window. An IPv6 address counts with its whole /64 network. A key that matches no active Retriever key never counts toward this budget. Random or old keys sent from a shared IP address therefore cannot block the valid keys sent from it. Retriever checks such keys against its database at most 20 times per 10 seconds for the whole service. Past that, it answers 503 with code key_check_busy and Retry-After, without checking the key. During a flood of unknown keys, a key created a moment ago (on another server instance) can get this 503 for up to 60 seconds: retry after Retry-After. These requests count as failures:
  • A 401 or 403 response to a request whose Authorization header holds a key that Retriever knew as active, for example a key revoked a moment ago.
  • A 401 or 403 response to a request that sends a session cookie that is not valid.
  • A 401 from the Unipile webhook to a request that sends a Unipile signature or auth header.
  • Any response that is not 2xx from an /oauth/ endpoint, for example an invalid authorization code (400) or an authorization request from a browser that is not signed in (302). A signed-in browser on /oauth/authorize does not count. These failures use a separate budget, so they do not block /mcp or /v1.
A 429 response never counts. A request with no credential at all does not count and is not refused by this limit, with one exception: /oauth/ endpoints (see above), where every call counts unless it carries a valid key or session. An example of a request that never counts is the first 401 that an MCP client gets before it starts the OAuth flow. A valid key never adds a failure. While Retriever checks a key that it has not seen in the last 10 minutes, that request holds one of the 100 places of its IP address. A burst of parallel requests with new keys from one IP address can therefore get 429 until the first answers come back. Retriever keeps the list of recent keys in memory, so the list is empty after each deploy. When the budget is spent, a key that authenticated in the last 10 minutes still gets through, except on /oauth/. Other requests from that IP address that carry a credential get 429 until the window moves. For a key that authenticated recently, Retriever also reads the key’s own budgets before the key lookup. When they are spent, the request gets 429 with the per-key message. A refused request does not use any budget.

MCP

Requests to the MCP endpoint use the budgets of the key that signs them.
  • Each JSON-RPC message sent to /mcp counts as one request on the key’s mcp budget. This budget has the same size as reads: by default, 600 MCP messages per minute per key.
  • The API calls that a tool makes count in their own classes, like the same calls made over REST. For example, service_execute counts one mcp message and one services request for POST /services/{serviceName}. The JSON-RPC message itself does not count in reads. The API calls a tool makes do count, so a key used for both MCP and REST shares its reads, services and heavy budgets between them.
  • A JSON-RPC batch (several messages in one HTTP request; most clients never send one) holds at most 10 messages, and each message counts. A larger batch gets 400 with JSON-RPC error -32600 before any message runs. This does not limit service_batch_execute, which takes 1 to 100 inputs.
  • A body larger than 10 MiB gets 413 with JSON-RPC error -32600. A body that is not valid JSON gets 400 with JSON-RPC error -32700.
  • On /mcp, a spent budget is an HTTP 429 with the body above and Retry-After, not a JSON-RPC error.
  • An MCP request with a valid key never adds a failure to the per-IP budget. The API calls that a tool makes are not checked against it.

Runs per organization

An organization executes at most 5 runs at the same time by default. This is not a request budget: it never returns 429.
  • Runs started with POST /v1/runs and with the MCP tool retriever_build_list_start share the same slots.
  • A further run is accepted with 202 and stays queued while all the slots are taken: by runs of your organization that are running or recovering, or by its older queued runs, which start first. Its queue field says why it waits and how many older runs of your organization wait before it (ahead). Read it on GET /v1/runs/{id}.
  • It starts on its own when one of them ends. Do not create it again.
  • Runs of other organizations are not delayed by yours.
  • Go jobs (/v1/go/runs) and conversations run from the app do not use these slots.

Request size

On the public API and MCP, a request body can be at most 10 MiB (10,485,760 bytes). POST /v1/workspaces/{id}/tables (table import) accepts up to 25 MiB (26,214,400 bytes). A larger body gets 413 with code body_too_large. The request is not processed. Do not retry it: the same body is refused again. Check the size before sending, and send less data per request, for example split an import into several tables.
On POST /mcp, the same 413 carries a JSON-RPC error. Its id is null, because the body was not read:
The limit applies after authentication: a request without a valid key gets 401 first. When the body declares its size with Content-Length, the API answers 413 without reading it. Your HTTP client may still be sending the body at that moment: some clients then report a connection reset (for example EPIPE or ECONNRESET) instead of the 413. Treat that error on a body over the limit as 413, not as a network error to retry.

Headers

Authenticated responses include:
RateLimit-Reset is in seconds.

429 response

When a per-key or per-user budget is exceeded, the API returns 429 with code rate_limited and the Retry-After header (in seconds):
When the failed-authentication budget of an IP address is spent, the message is different:
The /oauth/ endpoints use the OAuth error format:
The number in the message is the same as Retry-After. Wait Retry-After seconds, then retry. When concurrency is exceeded, Retry-After is 1.
Values may be adjusted by the Retriever team. Read the headers rather than hard-coding numbers.
For a client that applies these rules, see Shared Node client.