Skip to main content
We are in the critical path of your login, so the honest question is not whether we will ever be unavailable. It is what your product does that minute. The answer this API is built around, and the one you should hold us to:
Our outage means “no new sessions”. It never means “everybody is logged out”.
Those are different failures by an order of magnitude. The first one is a sign-in form that says “try again in a moment”. The second one is every one of your customers thrown out of a product that is working fine.

Why the difference exists

Because verification does not reach us. A contact session buys a short-lived JWT, and your backend verifies it against two published documents rather than by calling us: All three are public, addressed by the one identifier your config already holds, and cacheable by anything between you and us. Which means: while we are down, a request to your backend carrying a valid token is answered by your backend, out of a document it already has. Nothing about that request touches us.

The headers, and the second one that matters

stale-while-revalidate covers a refetch that is slow. stale-if-error covers one that comes back an error, times out, or cannot be made at all — and it is the directive that actually describes our outage. A cache in front of these documents honours both; most JWKS clients honour them too. If you put our documents behind your own CDN or reverse proxy, do not strip them.
Everything else on this API answers Cache-Control: no-store, and that is deliberate: those responses are the answer to somebody’s credential, and a shared cache holding one hands one person’s data to the next caller. Do not configure a cache in front of them, and do not “fix” the header.

What stops and what continues

Read this as two columns rather than as a list of outages. The left column is what you should expect to see fail; the right is what must keep working, and what to open a ticket about if it does not. Two things are worth stating because they are the ones people get wrong: A token refresh is not a new session. POST /v1/contact/token is a read on our side — it does not create anything. It is the call your SDK makes every few minutes for as long as somebody is signed in, and it is built to keep answering in conditions where a sign-in would not. Treat a failure there as transient. Exceeding a limit never blocks a sign-in. Neither the active-contact meter nor the send allowance is read by any authentication path. A limit produces a warning and an invoice, never a 402 in front of your users.

The four lines on your side

Our half is above. This is yours, and it is short.
1

Cache the key set

Fetch GET /v1/jwks/{publishableKey} once and cache it. The libraries in Session tokens do this for you. A verifier that fetches per request has moved our uptime back in front of every request to your API — the one thing the JWT exists to avoid.
2

Poll the revocation list, and keep verifying when you cannot fetch it

A list you could not refresh degrades to “I cannot revoke faster than the token expires” — which is the bound that always existed. It must never degrade to refusing everything. A revocation list that locks your customers out whenever it is unreachable is a worse failure than the one it prevents.
3

Retry a failed refresh; never sign somebody out over one

getToken() failing is a network event, not a logout. Clear a session when the API says the session is gone — a 401 on the contact surface — and not because a call did not come back.
4

Render your sign-in screen from a cached config

GET /v1/config/{publishableKey} tells a screen what to draw. Cache it (the header already asks you to) so the form renders even on a minute when we cannot answer. A screen that cannot paint because a configuration read failed is our outage becoming yours for no reason.
@userkit/nextjs’s verifyContactToken already implements all four, and claims.revocationCheck tells you which of fresh, stale or unavailable you are in. Pass requireRevocationCheck: true for the handful of actions where unavailable should refuse — and leave it off everywhere else, which is the whole point.

What we do not degrade

One thing runs the other way, and it is worth knowing before it surprises you: a captcha that is configured and cannot be verified refuses the sign-up with 503 captcha_unavailable. It does not pass. That is not an oversight in the degradation story, it is the one place the story inverts. A control that opens whenever its provider is slow is missing on precisely the day the traffic making it slow is the reason it was turned on. If you would rather a provider outage let sign-ups through, the decision is yours to make by turning the requirement off — not one we make for you during an incident.