> ## 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: "https://sumvin.com/?interest=early_access&utm_source=docs&utm_medium=banner&utm_campaign=early_access#waitlist",
  payments: "https://sumvin.com/?interest=payments&utm_source=docs&utm_medium=banner&utm_campaign=payments#waitlist",
  openBanking: "https://sumvin.com/?interest=open_banking&utm_source=docs&utm_medium=banner&utm_campaign=open_banking#waitlist",
  developers: "https://sumvin.com/developers?interest=cli,agent_plugin&utm_source=docs&utm_medium=banner&utm_campaign=developers#access",
  verifiers: "https://sumvin.com/developers?interest=verifiers&utm_source=docs&utm_medium=banner&utm_campaign=verifiers#access"
};

<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 |
| Website token check | `GET https://sis.sumvin.com/v0/sis/pint/tokens/{jti}/status` | 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.

The offline check tells you the token is genuine, addressed to you and unexpired. It can't tell you whether the person has revoked the mandate since the token was signed: for that, run the [website token check](#website-token-check).

### From the CLI

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

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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).

## Website token check

For a token addressed to your hostname. It answers one question, with no key and no account: does the mandate behind this token still stand?

Run the offline check first, then pass the token's `jti` claim:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
curl "https://sis.sumvin.com/v0/sis/pint/tokens/$JTI/status"
```

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "_links": {
    "self": { "href": "/v0/sis/pint/tokens/pint-3f2a9c0e4b7d4e1fa5c6b8d9e0f1a2b3/status" }
  },
  "active": false,
  "reason": "revoked"
}
```

| Field | Means |
| - | - |
| `active` | `true` while the mandate has not been revoked and has not expired. **Act only on `true`.** |
| `reason` | `null` when active. Otherwise `revoked` (the person withdrew this mandate, or a larger one it was narrowed from) or `expired`. A token can be inactive with no reason; treat it the same way. |

What a website can and can't check:

| Can check | How |
| - | - |
| The token is genuine, from Sumvin and addressed to you | Offline check |
| It hasn't expired | Offline check, and this check |
| The person hasn't revoked it | This check |

| Can't check | Why |
| - | - |
| Who the person is | A website-addressed mandate never carries it. |
| Anything about the mandate beyond whether it stands | This check returns only `active` and `reason`. |

Rules of the website token check:

* **It answers only for tokens addressed to a website.** Any other `jti`, whether it exists or not, returns the same `404`.
* **Answers are never cached.** A revocation shows on the next check. The response carries `Cache-Control: no-store`; don't cache it yourself for longer than you are willing to honour a revoked mandate.
* **Call it from your server.** Browsers can't call it from another site.
* **It is rate limited per IP address**, at 120 checks a minute. Past that it returns `429` with `Retry-After`.

## 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={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "_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` only when the mandate is active and has not reached its expiry. **Act only on `true`.** |
| `reason` | `null` when valid. Otherwise one of `revoked` (withdrawn), `expired` (reached its expiry), `pending` (prepared but never signed) or `consumed` (already spent against). A revoked mandate reports `revoked` even once past its expiry; any other mandate past its expiry reports `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` | Online check: no mandate with this identifier has a token addressed to your organisation. Website token check: no token addressed to a website has this `jti`. | Don't act. |
| `429` | `PINT-429-002-R` | Website token check: too many checks from your IP address. | Wait for `Retry-After`, then try again. |
| `503` | `SYS-503-003-R` | Website token check: the check is briefly unavailable. | Try again shortly. Don't act until it answers. |
| `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).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.