Each public endpoint has a threshold, counted per minute and per real IP address — the visitor's, not the proxy's.
| Endpoint | Limiter | Per minute and per IP | Per hour and per website |
|---|---|---|---|
POST …/track |
api-track |
60 | 50 000 |
GET …/signature |
api-signature |
30 | — |
GET …/captcha/altcha/challenge |
api-captcha-challenge |
30 | — |
GET …/forms/config |
api-forms-config |
60 | — |
POST …/submit |
api-submit |
depends on the client's plan, 5 by default | 1 000 |
Submission limiting is counted per IP and per website: two visitors behind the same internet gateway, on the websites of two different clients, do not share the same bucket.
The two hourly ceilings
Submissions and tracking carry a second counter, per website and per hour, all IP addresses together: a flood spread over many addresses passes the per-IP threshold, but not that one.
| Counter | Default | Environment variable |
|---|---|---|
| Submissions, per website and per hour | 1 000 | PLATFORM_SUBMISSIONS_PER_WEBSITE_PER_HOUR |
| Tracking, per website and per hour | 50 000 | PLATFORM_TRACKS_PER_WEBSITE_PER_HOUR |
They are deliberately wide: a website that reaches them is under attack, not busy. The
submission counter is incremented before the captcha is verified — a tighter ceiling
would let a third party block a client's leads. Either counter answers a plain 429, like
the per-minute thresholds.
IPv6 counts by /64
A bucket is the IPv4 address, or, for an IPv6 address, its /64 prefix — a single
subscriber is given a whole /64, and changing address inside it must not reset their
counter. 2001:db8:1:2:3:4:5:6 and 2001:db8:1:2::ff share the bucket
2001:db8:1:2::/64. This applies to every limiter above.
The address itself comes from CF-Connecting-IP, and only when the connection actually
arrives from a Cloudflare range; otherwise it is the address of the connection. A forged
header therefore buys nothing.
429 response
HTTP/1.1 429 Too Many Requests
Retry-After: 37
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
Honor Retry-After: it is the number of seconds before the next useful attempt. Retrying
earlier only extends the block.
Two barriers, not one
These thresholds are the fine-grained barrier, enforced by the application. A broader barrier is enforced upstream, at the network edge, with deliberately more permissive thresholds: it absorbs floods without waking the application.
In practice: a burst of requests from a single address may be blocked at the edge, and
the response is then not the application's JSON but a block page. A fetch() that
receives HTML where it expects JSON should look at that barrier.
Behaving well
- Only request a signature when you need one: it is valid for ten minutes.
- Only call tracking once per page view.
- Keep your API key in configuration: it is a long-lived secret, not something to fetch.
- On
429, wait; on403, fix the cause — a403is never solved by insisting.
API