> ## Documentation Index
> Fetch the complete documentation index at: https://docs.userkit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Availability and degradation

> What happens to your product when ours is having a bad day, and the four lines of your code that decide it.

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:

<Note>
  **Our outage means "no new sessions". It never means "everybody is logged out".**
</Note>

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](/en/customer-auth/session-tokens), and your backend verifies it against two
**published documents** rather than by calling us:

| Document                               | What it answers                     | Cached for                       |
| -------------------------------------- | ----------------------------------- | -------------------------------- |
| `GET /v1/jwks/{publishableKey}`        | which keys signed a valid token     | 5 min, servable stale for 24 h   |
| `GET /v1/revocations/{publishableKey}` | which sessions ended recently       | 15 s, servable stale for 15 min  |
| `GET /v1/config/{publishableKey}`      | what a sign-in screen should render | 1 min, servable stale for 10 min |

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

```
Cache-Control: public, max-age=300, stale-while-revalidate=86400, stale-if-error=86400
```

`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.

<Warning>
  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.
</Warning>

## 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.

| While we are degraded                     | Stops                                                                                                            | Continues                                                             |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| Any unavailability on our side            | Sign-up, sign-in, magic links, email codes, social sign-in, waitlist joins — everything that **mints** a session | Every session already minted, for as long as it lives                 |
|                                           | Reading a contact's own record, their devices, their teams                                                       | Your backend verifying tokens against the cached key set              |
|                                           |                                                                                                                  | Your backend refusing revoked sessions, from the last list it fetched |
| Email delivery is degraded on our side    | Verification links, magic links and codes arrive late or not at all — so those sign-ins do not complete          | Password sign-in, social sign-in, and every existing session          |
| A capability is unavailable on the server | The endpoints that need it answer `501` and say which capability it was                                          | Everything that does not need it                                      |

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.

<Steps>
  <Step title="Cache the key set">
    Fetch `GET /v1/jwks/{publishableKey}` once and cache it. The libraries in
    [Session tokens](/en/customer-auth/session-tokens#verifying) 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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>
</Steps>

<Note>
  `@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.
</Note>

## 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.
