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

# Checks and errors

> Every step of checking a Stamped Mandate as a verifier: the offline check, and for services that hold the signed request, the exchange and online check, with their responses and error codes.

export const waitlists = {
  earlyAccess: "",
  payments: "",
  openBanking: "",
  developers: "",
  verifiers: ""
};

<Note>
  **Private preview:** this is rolling out and may not be available on your account yet.{waitlists.earlyAccess ? <> <a href={waitlists.earlyAccess}>Request early access →</a></> : null}
</Note>

This page is the reference for the [verifier quickstart](/sdk/verify/quickstart).

An agent presents a Stamped Mandate as a token in the `x-sumvin-pint-token` request header, with no `Bearer` prefix, addressed to your site's hostname. You check it offline, on your own server, with no key.

The exchange and the online check are only for a service that already holds the signed request a person approved, for example for the service's own app. An agent never sends a site the signed request. Both calls authenticate with your API key as `Authorization: Bearer <key>`.

| Step                                       | Where                                                             | Key permission                            |
| ------------------------------------------ | ----------------------------------------------------------------- | ----------------------------------------- |
| Read the token                             | The `x-sumvin-pint-token` header on the agent's request           | None                                      |
| Offline check                              | Your server, with `https://jwks.sumvin.com/.well-known/jwks.json` | None                                      |
| Exchange (signed request holders only)     | `POST https://sis.sumvin.com/v0/sis/token/pint`                   | Exchange, included in a Standard key      |
| Online check (signed request holders only) | `GET https://sis.sumvin.com/v0/sis/pint/{id}/status`              | Read mandates, included in a Standard key |

## Offline check

Verify the token with any standard JWT library against:

| Setting   | Value                                                                                                    |
| --------- | -------------------------------------------------------------------------------------------------------- |
| Key set   | `https://jwks.sumvin.com/.well-known/jwks.json`                                                          |
| Algorithm | `ES256`                                                                                                  |
| Issuer    | `https://sis.sumvin.com`                                                                                 |
| Audience  | Your hostname, such as `reservations.example.com`, or your Organisation ID for a token from the exchange |

Pin the algorithm to `ES256`, the only one Sumvin signs with, so a token signed any other way is refused rather than trusted. Pick the key by the identifier in the token's header, and let the library refresh the key set: keys rotate, so never copy one into your code. A token that fails any part of the check is a refusal.

<Note>
  **Coming soon:** an online check for tokens addressed to a website. Until then, the offline check is the whole check: it tells you the token is genuine, addressed to you and unexpired, but not whether the person has revoked it since.
</Note>

### From the CLI

The [CLI](/reference/cli/pint) runs the same check:

```bash theme={null}
sumvin pint verify "<token>" --offline --audience "<your hostname or Organisation ID>"
```

With `--offline` it makes no Sumvin API calls, so the revocation check reports as unverified. An unverified check never counts as a pass, so the overall answer is no and the command exits `2`; read the signature, issuer, expiry and audience lines individually. The CLI's own revocation check only answers for mandates you own. The CLI is itself in [Private preview](/get-started/cli/install).

## Exchange

For a service that already holds the person's signed request.

Send the signed request the person approved, unchanged, with `audience` set to your Organisation ID. Any other audience is refused. During the preview, some mandates can't be exchanged yet; those return `PINT-403-009`.

| Answer | Means                                                                                 |
| ------ | ------------------------------------------------------------------------------------- |
| `201`  | A token addressed to your organisation was issued.                                    |
| `208`  | This request was already exchanged for your organisation. The body is the same token. |

The body carries `sig` (the token), `id` (the mandate identifier), `scopes` and `expires_at` (epoch milliseconds).

One signed request can be exchanged by up to ten parties, each getting its own token addressed to itself. When the mandate expires or the person revokes it, every token issued against it stops working together.

The person's verification is checked again at every exchange. A request signed while they were verified yields nothing new if their verification has lapsed since.

## Online check

For a token from the [exchange](#exchange), addressed to your organisation.

```bash theme={null}
curl "https://sis.sumvin.com/v0/sis/pint/$(printf %s "$MANDATE_ID" | jq -sRr @uri)/status" \
  -H "Authorization: Bearer $SUMVIN_API_KEY"
```

URL-encode the mandate identifier: it contains colons.

```json theme={null}
{
  "_links": {
    "self": { "href": "/v0/sis/pint/sr%3Aus%3Apint%3A3f9c2a7e5b1d4c8e9a6f0b2d7e4c1a58/status" },
    "pint": { "href": "/v0/sis/pint/sr%3Aus%3Apint%3A3f9c2a7e5b1d4c8e9a6f0b2d7e4c1a58" }
  },
  "id": "sr:us:pint:3f9c2a7e5b1d4c8e9a6f0b2d7e4c1a58",
  "valid": false,
  "reason": "revoked",
  "revoked_at": 1758620000000
}
```

| Field        | Means                                                                           |
| ------------ | ------------------------------------------------------------------------------- |
| `valid`      | `true` when the mandate is neither revoked nor expired. **Act only on `true`.** |
| `reason`     | `null` when valid; otherwise `revoked` or `expired`.                            |
| `revoked_at` | When it was revoked, in epoch milliseconds. Only set when revoked.              |

Rules of the online check:

* **It answers only for mandates addressed to you.** A mandate with no token addressed to your organisation returns `404`, whether or not it exists. This is how one verifier is kept from probing another's mandates.
* **Answers can be up to 30 seconds old.** The answer for each mandate is cached per organisation for 30 seconds.
* **`valid` is this mandate's own state.** The fuller *stands* answer in [What a verifier checks](/concepts/mandates/verification#stands-not-status) also accounts for any larger mandate this one was narrowed from. That answer is available through the connector's `mandate_verify` tool; see [Check a mandate](/guides/check-a-mandate).

To read the mandate's scopes, status and expiry, call `GET https://sis.sumvin.com/v0/sis/pint/{id}` with the same key. It follows the same rule: `404` unless a token for the mandate is addressed to your organisation.

## Errors

Errors come back as a problem document with an error `code`, except a missing `Authorization` header, which returns `401` with `{"detail": "Not authenticated"}`.

| Status | Code           | Means                                                                                        | What to do                                   |
| ------ | -------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------- |
| `401`  | `SIS-401-001`  | The key is invalid, expired or revoked.                                                      | Check the key, or create a new one.          |
| `401`  | `SIS-401-002`  | The key isn't attached to an organisation.                                                   | Create the key from the developer dashboard. |
| `403`  | `SIS-403-003`  | The key lacks the permission this call needs.                                                | Use a Standard key.                          |
| `404`  | `PINT-404-001` | No mandate with this identifier has a token addressed to your organisation.                  | Don't act.                                   |
| `400`  | `PINT-400-002` | Exchange: `audience` isn't your Organisation ID.                                             | Set it to your Organisation ID.              |
| `403`  | `PINT-403-009` | Exchange: during the preview, some mandates can't be exchanged yet, and this is one of them. | Don't act.                                   |
| `409`  | `PINT-409-002` | Exchange: the mandate has been revoked.                                                      | Don't act.                                   |
| `409`  | `PINT-409-007` | Exchange: the signed request has already reached as many parties as it can.                  | Ask for a new mandate.                       |
| `410`  | `PINT-410-001` | Exchange: the mandate has expired.                                                           | Don't act.                                   |

More codes are listed in [Errors](/reference/errors).
