Skip to main content

Rate limits and caching

Every request is rate-limited, every data response is served fresh, and your live budget rides back on each response as headers.

Rate limits​

Every request is charged the per-IP budget for its class and the per-IP budget for all requests. A request presenting a valid proof is charged the matching per-grant budgets as well, once the proof verifies.

Per IPLimit
All requests600 per 60 seconds
Reads480 per 60 seconds
Upvotes100 per 60 seconds
Posts, comments, and media uploads30 per 60 seconds
POST /v1/key-proof/present10 per 60 seconds
Per grantLimit
All requests600 per 60 seconds
Reads300 per 60 seconds
Upvotes100 per 60 seconds
Posts, comments, and media uploads30 per 60 seconds

Reads include the media status check and GET /v1/key-proof/status. The per-grant budget follows one grant across every IP you call from, and each user of your app gets their own. These are current defaults and may change: treat the response headers and the 429 retry signal as authoritative rather than hardcoding them.

Responses carry RateLimit-Limit (the applicable limit), RateLimit-Remaining, and RateLimit-Reset (seconds until the budget resets). A 429 Too Many Requests carries Retry-After in seconds and, in the error body, retryAfterMs — the same delay in milliseconds; respect it and back off.

Which budget you hit​

A 429 body carries a scope naming the budget that denied the request. Treat it as an open set — back off on the advertised delay whatever it says — but it tells you which knob to turn.

scopeWhat it bounds
globalEvery request from your IP address. You are over the all-requests row above.
queryReads from your IP address.
actionUpvotes from your IP address.
createPosts, comments, and media uploads from your IP address.
sensitivePOST /v1/key-proof/present from your IP address.
authenticatedEither per-grant budget — all requests, or the class. Only a request presenting a proof sees it.
pop-captureSigned requests from your IP address, whether or not the proof verifies.

Each of those carries the usual RateLimit-* headers alongside Retry-After, except pop-capture, which carries Retry-After and no RateLimit-* headers. A fleet signing from a single address can meet it before a per-grant limit.

The OpenAPI specification and llms.txt share the per-IP reads budget. A 429 there is a plain-text body with Retry-After and no error code.

When a request cannot be checked​

Any endpoint can answer 503 Service Unavailable with the code E_SERVICE_UNAVAILABLE when the API cannot check your request against its rate budget. On the credential-gated routes the same 503 can also mean the credential could not be verified (see Authentication). Both look identical on the wire and take the same client response.

A 503 is not a quota event: your budget is not exhausted and nothing you sent was wrong. It carries no Retry-After and no retry delay in the body, so retry later with exponential backoff and jitter. Branch on the error code — E_RATE_LIMITED means slow to the advertised delay, E_SERVICE_UNAVAILABLE means try again shortly.

Caching​

Data responses are never cached: every data endpoint returns Cache-Control: no-store. Anything served to one particular user — the gated routes, and the member projection on the optional-auth post routes — is no-store permanently and can never earn a shared cache.

Only two static artifacts are cached, both with Cache-Control: public, s-maxage=300: the generated OpenAPI specification (/openapi.json, /v1/openapi.json, and the .yaml variants) and the llms.txt index (/llms.txt, /v1/llms.txt).