# Tokens and clearances

> Single-use tokens (receipts) for one operation, reusable clearances for gateway routes, and how each is bound and verified.

A passed verification gives the page a **token**. Actions configured for
it also give a **clearance**. They answer different questions:

| | Token (receipt) | Clearance |
| --- | --- | --- |
| Answers | "May this one operation happen?" | "Has this visitor passed recently?" |
| Use | Once | Any number of times until it expires |
| Lifetime | `RECEIPT_TTL_SECONDS`, default 120 s (30–300) | `CLEARANCE_TTL_SECONDS`, default 1800 s (60–86,400) |
| Bound to | site, action, origin, expiry, key | site, origin, expiry, key |
| Checked by | your server: `POST /v1/siteverify`, or the gateway | the gateway (`/v1/gateway/check`) |
| Carried in | the `agentgate_token` form field, or a header you choose (the gateway reads `X-AgentGate-Token`) | the `X-AgentGate-Clearance` header, or the `agentgate_clearance` cookie |

## Tokens

A token starts with `agr1.` and is signed by AgentGate. Treat it as an
opaque string: its format may change. It records the site, the action it
was issued for, the page origin, when it was issued and expires, and the
decision ID. It holds no scores or personal data.

- **Single use.** Redemption is atomic: of any number of concurrent
  redemptions, one succeeds and the others get `token_used`.
- **Safe retries.** Redeeming again with the same `requestId` within the
  token's lifetime returns the same success with `idempotentRetry: true`.
  This covers a lost response; it never authorises a second operation.
- **Bound to its action.** `expectedAction` is required at siteverify, so a
  token earned on a newsletter form cannot sign up an account.
- **Short-lived.** Call `execute()` (or let the widget verify) at submit
  time, not at page load.

Details: [Validate tokens](/docs/siteverify).

## Clearances

For actions registered with `"clearance": true` (`--action name:clearance`
on the command line), a passed verification also returns a clearance: a
signed value that the [gateway](/docs/gateway) accepts on routes of
clearance actions instead of a fresh token, until it expires. This is the
"pass once, browse for a while" pattern. A clearance earned for one
clearance action is accepted on the routes of every clearance action of the
site, but only on the origin that earned it: the gateway compares it with
the protected request's scheme and host (`X-Original-Proto`,
`X-Original-Host`), so the page and the protected routes must share an
origin.

`AgentGate.execute()` returns it as `clearance`, with `clearanceExpiresAt`
(Unix milliseconds). Send it on requests the gateway protects:

```js
const { clearance } = await AgentGate.execute({ action: "browse" });
await fetch("/api/catalog", { headers: { "X-AgentGate-Clearance": clearance } });
```

When AgentGate's browser API is served from your own origin (you
reverse-proxy `/v1/browser/*`), the clearance is also set as the first-party
cookie `agentgate_clearance` (HttpOnly, SameSite=Lax, Secure over HTTPS), so
the browser sends it without code. Third-party cookies are never used.

> [!WARNING]
> A clearance is a bearer value: it is bound to the site and origin, not to
> a device, and a copied value works from elsewhere until it expires. Keep
> the lifetime short on sensitive routes and use single-use tokens for
> state-changing operations.

Clearances are Ed25519-signed, so they keep verifying after a key rotation
until the old key is retired.

## What to send where

| You protect | The page sends | Your server does |
| --- | --- | --- |
| A form post handled by your backend | `agentgate_token` field (widget) | `POST /v1/siteverify` |
| A `fetch` call handled by your backend | the token, in a header or body field you choose | `POST /v1/siteverify` |
| A route behind the gateway, one operation | `X-AgentGate-Token` header | nothing: the gateway checks and spends it |
| Routes behind the gateway, repeated reads | `X-AgentGate-Clearance` header or the cookie | nothing: the gateway checks it |
