uk_sk_… key, they are a capacity
contract. That is the one surface whose caller is a program rather than a
person, so it counts per key and publishes the numbers in headers on every
response — your client can pace itself instead of discovering the limit by
tripping it.
Being limited
429
Retry-After carries the window in seconds. Wait it out; retrying sooner only
consumes the next window. On the machine surface the window is a minute, so it is
never more than 60.
The limits
Staff authentication
Invitations
Customer plane
The per-key limit on boot exists because the publishable key is the tenant boundary
a flood arrives through: bots and landing-page traffic would otherwise mint visitor
rows forever.
The contact’s own surface
Per session, not per IP. Behind carrier NAT one address is thousands of people,
so an IP bucket on a signed-in surface throttles a crowd for what one of them
did — and the session is the thing actually spending the work.
/v1/contact/token
is why the number exists at all: every call is a key decrypt and a signature.
The machine surface
Per key, not per IP. Your backend calls this from a datacenter address that
all of your servers share, and it holds no session — the key is the only thing
that identifies the caller, and the only thing you can act on.
Two consequences worth planning around:
- Your own segmentation is your isolation. A key per service — the app, the worker, the nightly job — means a backfill running flat out cannot throttle your sign-in path: its own 1000 stops it before it has spent more than a third of the shared 3000. One key for everything is one bucket for everything.
- Minting keys does not buy allowance. The environment ceiling is what makes the per-key limit mean anything: the fourth key shares the same 3000.
The rest of the authenticated surface — the panel’s own session — is not
throttled beyond the sign-in endpoints above. The credential is the gate, and it
is revocable.
Reading the headers
Every response on the machine surface carries the current state, a200 as much
as a 429.
RateLimit-* names are the IETF draft’s, the X-RateLimit-* names are what most
clients written in the last decade already read. Use whichever your HTTP client
understands and ignore the other.
The one place they differ is the reset, and it is not an oversight — each name
means what its own convention says it means. RateLimit-Reset is how many
seconds to wait; X-RateLimit-Reset is the timestamp to wait until.
X-RateLimit-Scope is there because two ceilings are in play and the numbers
describe whichever is closer to running out. RateLimit-Limit: 3000 on a key
whose own limit is 1000 means the environment is the binding one: another of your
keys is spending the shared allowance.
Failing open, failing closed
Two postures, chosen per endpoint. The counters live in a store shared by every instance of the API. When that store cannot be reached, each instance counts on its own instead — the limits stay, and they get looser rather than disappearing. That is deliberate, and it is the one place this platform does not degrade gracefully. Everything else optional turns off without its dependency; a public sign-up behind no limiter at all is the whole security posture silently off, at exactly the moment an attacker is most likely to be the reason. What you may notice while it lasts: a limit briefly allowing more than the number above, because several instances are each counting to it. Nothing answers differently, and there is nothing to handle.RateLimit-Remaining is then that one instance’s remainder rather than the whole
fleet’s, so it reads a little generous. The headers keep coming rather than
disappearing for the duration — a contract that vanishes when a dependency does
is one no client can branch on.
Staying under them
1
Read the headers on the machine surface
RateLimit-Remaining on the response you already have is cheaper than a
429. A batch job that pauses when it drops below a margin never trips
anything.2
Retry on 429, with backoff
Honour
Retry-After when it is present; back off exponentially when it is not.3
Do not retry a 4xx that is not 429
A
400 or a 403 will answer the same way every time.4
Debounce boot
One boot per page load, not one per component that wants the contact.
5
Rate-limit your own resend buttons
Verification and magic-link resends are 5 an hour. A button with no cooldown
burns them in seconds and the user sees nothing but a failure.