Skip to content

Access & rate limits

No sign-up, no account, nothing to send. Every caller gets the same access, rate-limited per IP.

Sign-up None
Rate limit 1000 requests per minute per IP, enforced by the app
Edge burst limit Cloudflare separately caps bursts at 170 requests per 10 seconds per IP, blocking for 10 seconds past it — see the edge burst limit below
Over the app’s limit 429, code rate_limited, header Retry-After (seconds)
Browser cache Cache-Control: public, max-age=300 (5 minutes); /v1/dump uses max-age=3600
Edge cache Responses are also cached at the Cloudflare edge for 10 minutes
ETag "<dataset_version>". Changes when the data, or the way responses are derived from it, changes — see dataset_version
CORS Any origin, GET only, no credentials — see CORS below
/health Never rate limited

Send If-None-Match with the last ETag you saw. If dataset_version hasn’t changed, the API returns 304 Not Modified with no body:

Terminal window
curl -D - -o /dev/null \
-H 'If-None-Match: "e9c25a6"' \
https://truefloat.app/api/v1/items/7/44/patterns
HTTP/2 304
etag: "e9c25a6"
cache-control: public, max-age=300
access-control-allow-origin: *
access-control-expose-headers: ETag, Retry-After

See Caching & conditional requests for how to use this in a poller.

Past 1000 requests in a minute from one IP, every further request in that minute is refused:

Terminal window
curl -i https://truefloat.app/api/v1/items/7/44
HTTP/2 429
content-type: application/json
retry-after: 60
access-control-allow-origin: *
access-control-expose-headers: ETag, Retry-After
{"dataset_version": "e9c25a6", "error": {"code": "rate_limited", "message": "Too many requests"}}

Wait Retry-After seconds before retrying. If a 429 ever arrives without the header, or without the JSON body, wait 60 seconds.

Cloudflare enforces a second, tighter limit in front of the app: no more than 170 requests in 10 seconds from one IP, or it blocks that IP for the next 10 seconds. This catches a short, sharp burst (a retry loop gone wrong, a page that fires one request per row of a big table) well before it could ever reach 1000 requests in a minute.

The edge’s block response comes from Cloudflare, not from the API, and carries no CORS headers. Outside a browser (curl, a server, a script) that makes no difference: it is an ordinary 429 response you can read. In a browser calling the API from another site, the browser withholds a response without CORS headers from your code: the fetch() call rejects with a plain network error (a thrown TypeError), not a 429 you can inspect.

Handle that the same way you handle a 429: back off and retry. There is no Retry-After to read from a response your code never sees, so the quickstart helper waits a fixed 10 seconds (long enough to outlast the block) and retries a network error up to three times.

The API is meant to be called from browser JavaScript on any site.

  • Every response from the API, errors, 429 and 304 included, carries Access-Control-Allow-Origin: * and Access-Control-Expose-Headers: ETag, Retry-After, so a script can read both headers. The headers are the same whether or not the request sent an Origin, and responses don’t vary on it, so one cached copy at the edge serves every caller.
  • GET only, no credentials: don’t send cookies (credentials: 'include'), there is nothing to send them for.
  • A preflight (OPTIONS) allows GET with an If-None-Match header, so a conditional request from the browser works too.