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 |
Conditional requests
Section titled “Conditional requests”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:
curl -D - -o /dev/null \ -H 'If-None-Match: "e9c25a6"' \ https://truefloat.app/api/v1/items/7/44/patternsHTTP/2 304etag: "e9c25a6"cache-control: public, max-age=300access-control-allow-origin: *access-control-expose-headers: ETag, Retry-AfterSee Caching & conditional requests for how to use this in a poller.
Retry-After
Section titled “Retry-After”Past 1000 requests in a minute from one IP, every further request in that minute is refused:
curl -i https://truefloat.app/api/v1/items/7/44HTTP/2 429content-type: application/jsonretry-after: 60access-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.
The edge burst limit
Section titled “The edge burst limit”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,
429and304included, carriesAccess-Control-Allow-Origin: *andAccess-Control-Expose-Headers: ETag, Retry-After, so a script can read both headers. The headers are the same whether or not the request sent anOrigin, and responses don’t vary on it, so one cached copy at the edge serves every caller. GETonly, no credentials: don’t send cookies (credentials: 'include'), there is nothing to send them for.- A preflight (
OPTIONS) allowsGETwith anIf-None-Matchheader, so a conditional request from the browser works too.