<!-- https://settled.tools/x401 · as Markdown for agents · updated 2026-10-13 -->

# x401, explained

*Updated October 13, 2026 · covers x401 draft 0.2.0, the current one, and 0.1.0 · by Settled*

x401 is an open protocol that lets a server ask for proof before it serves a request. The server answers with a `PROOF-REQUEST` header naming the credential it wants, and the agent retries with the proof. It is the identity counterpart to x402, which asks for payment the same way. This page explains how x401 works in draft 0.2.0, what changed from 0.1.0, how it pairs with x402, and how to test a client against a live verifier.

## What x401 is

x401 adds a challenge and a response for proof to plain HTTP. A website or API that needs to know something about whoever is calling, such as a verified identity, an age, a membership, an accreditation or a payment history, says so in a response header. The caller, usually an AI agent, gets a matching credential presentation from a wallet and sends it on a retry. The server checks it and serves the request.

Proof (proof.com) published x401 in June 2026 as an open, issuer-neutral protocol. The specification is a working draft under the Apache-2.0 license, at [x401.proof.com](https://x401.proof.com/) (Proof also gives it as x401.id) with its source on [GitHub](https://github.com/proof/x401). The current draft is 0.2.0; 0.1.0 is the previous one. Proof's announcement thanks individual contributors from Circle, OpenAI, Okta, MATTR and Lightspark, and Circle has called itself an early adopter and co-endorser. Proof has also said it will bring x401 to the FIDO Alliance's work on agent authentication. It is not a formal standard yet.

The idea behind it: an agent acting for a person or a company should be able to show what that person or company has authorized, without a sign-up form, an API key or a password. x401 carries the proof. It does not issue credentials, and each server decides which issuers it trusts. Payment stays with HTTP 402 and a payment protocol such as x402.

## How a request works

1. **Agent to server:** The agent calls a protected route with no proof.
2. **Server to agent:** The server answers, usually with `401 Unauthorized`, and a `PROOF-REQUEST` header: base64url JSON describing the credential it wants, with a fresh nonce.
3. **Agent to wallet:** The agent gets a presentation that satisfies the request: from its own wallet, through the browser's Digital Credentials API, by relaying the request to a wallet or a remote service, or out of band.
4. **Agent to server:** The agent retries the same request with a `PROOF-RESPONSE` header carrying the result.
5. **Server to agent:** If the proof checks out, the server answers normally, often `200 OK`. If not, it says why in a `PROOF-RESULT` header and can ask again.

So that it does not have to prove itself on every call, the agent can trade a verified result at the server's OAuth token endpoint for a short-lived Bearer token and send that instead. The status code does not carry x401: it describes the whole response, so a server can send `PROOF-REQUEST` with 401, 200 or whatever fits. Payment still uses 402.

## The three headers in draft 0.2.0

| Header | Direction | Carries |
| --- | --- | --- |
| `PROOF-REQUEST` | Server to agent | The x401 payload: what proof is needed, and how to get a token |
| `PROOF-RESPONSE` | Agent to server | A Result Artifact (the presentation, inline or by reference) or an x401 Token Object |
| `PROOF-RESULT` | Server to agent | The outcome, including an x401 error object when the proof fails |

Each value is a single JSON object, base64url-encoded without padding.

## What the server asks for

The `PROOF-REQUEST` payload is flat. Its members:

| Member | What it holds |
| --- | --- |
| `scheme`, `version` | `"x401"` and the payload version, `"0.2.0"` |
| `credential_requirements` | A Digital Credentials API request the server wrote: `digital.requests`, each with a `protocol` (`openid4vp-v1-signed`, recommended, or `openid4vp-v1-unsigned`) and its OpenID4VP `data`: the DCQL query naming the credential and the claims, the nonce and the response mode |
| `oauth` | Optional token exchange: `token_endpoint`, with `audience` or `resource` |
| `request_id`, `satisfied_requirements` | Optional hints for reusing a token across routes |
| `payment` | Optional and informational: whether payment is also required, and which scheme, such as x402 |

Because the request is a standard Digital Credentials API request, a browser agent can pass it to `navigator.credentials.get` (a signed request works only from an origin listed in its `expected_origins`), and an agent without a browser can relay it to a wallet or a service that handles such requests. The credential format is whatever the query names: an SD-JWT VC (`dc+sd-jwt`), an ISO mdoc, and so on.

## How the agent answers

On the retry, `PROOF-RESPONSE` carries a **Result Artifact**:

```
{
  "credential_result": {
    "protocol": "openid4vp-v1-unsigned",
    "data": { "vp_token": { "<query id>": ["<presentation>"] } }
  },
  "request_id": "…",   // optional hint
  "agent_id": "…"      // optional; used only when the server binds the agent
}
```

Instead of `credential_result` it can carry `credential_result_uri`: an HTTPS link to a result delivered by reference, which the verifier issued for that one result, with an optional `expires_at`. That suits large or remotely generated presentations. For a token, the agent posts the same artifact to the token endpoint (OAuth 2.0 token exchange: `grant_type=urn:ietf:params:oauth:grant-type:token-exchange`, `subject_token_type=urn:x401:params:oauth:token-type:result_artifact`) and gets a Bearer token, which it then sends in `Authorization` or wraps in an x401 Token Object in `PROOF-RESPONSE`.

## What the proof is bound to

A presentation has to be bound to the request it answers, or anyone could replay it. In x401 0.2.0 that binding comes from OpenID4VP:

- **Signed requests** name the server in `client_id` and carry its signature, so the wallet can tell who is asking, and `expected_origins` limits which web origins may invoke them. Over the Digital Credentials API, OpenID4VP makes the result's audience the invoking origin (`origin:https://…`), even for a signed request; a relayed signed request binds the audience to the `client_id`.
- **Unsigned requests** rely on the invoking web origin and the server's nonce; relayed instead of invoked from a web page, they are bound by the nonce alone. The spec warns that a result accepted on the strength of the nonce alone can be harvested and relayed, whether through a relay or from a malicious page. It asks verifiers to use fresh nonces with short expiries, and to compensate, for example by authenticating the agent or constraining the origin.
- **Binding the agent** itself is optional. Deployments that need it add caller authentication on top, such as HTTP Message Signatures (Web Bot Auth), mutual TLS, DPoP or a workload identity.

The nonce lives inside the OpenID4VP request. A server that keeps no state can make it self-contained, signing the route, the policy and the expiry into it, and check it when the answer comes back.

## x401 and x402

The two protocols answer different questions with the same pattern. x402 asks whether the caller will pay: a `402 Payment Required` response with a quote, and a retry with the payment. x401 asks whether the caller can prove something: a credential request, and a retry with the proof. They stay separate protocols, but one route can ask for both. In the draft's main flow the agent proves first, and the retry that pays carries the proof, or its token, along with the payment; other orders are still open questions in the draft. The x401 payload's `payment` member only tells the agent whether payment will also be needed.

Together they cover what a paid API used to need an account for: who may call (x401) and who pays (x402), with no API keys to issue or rotate.

## What changed from 0.1.0 to 0.2.0

|  | 0.1.0 | 0.2.0 |
| --- | --- | --- |
| Server asks | `PROOF-REQUIRED` | `PROOF-REQUEST` |
| Agent answers | `PROOF-PRESENTATION`, with a VP Artifact | `PROOF-RESPONSE`, with a Result Artifact |
| Errors and results | `PROOF-RESPONSE` | `PROOF-RESULT` |
| Who writes the OpenID4VP request | The agent, from a `proof` object (`presentation_protocol`, `dcql_query` or `scope`, `challenge`, `issuers`) | The server, as a Digital Credentials API request in `credential_requirements` |
| Freshness | A challenge in the form `x401:<verifier>:<nonce>` | The OpenID4VP nonce inside the request |
| Token exchange `subject_token_type` | `urn:x401:params:oauth:token-type:vp_artifact` | `urn:x401:params:oauth:token-type:result_artifact` |
| Binding | The agent was the OpenID4VP client and the audience; `agent_id` was required | The result is bound to the verifier or the invoking origin (a relayed unsigned result: the nonce only); binding the agent is optional and `agent_id` is a hint |
| `oauth` | Required, inside `proof` | Optional, at the top level |
| Trusted issuers | An optional `issuers` hint | Trusted authorities named in the DCQL query |
| Requests by `scope` | An alternative to a DCQL query | Removed |
| Results by reference | Not defined | `credential_result_uri`, issued by the verifier |

Watch out for `PROOF-RESPONSE`: it changed direction. In 0.1.0 the server sent it, with errors; in 0.2.0 the agent sends it, with the proof. A server that supports both drafts, as Settled does, sends `PROOF-REQUEST` and `PROOF-REQUIRED` together and reads whichever answer comes back. (Settled's 0.2.0 nonce happens to keep the 0.1.0 `x401:<verifier>:<nonce>` shape; to a 0.2.0 client it is just an opaque value.)

## Try it against a live verifier

Settled runs an x401 verifier you can test a client against. [`GET https://settled.tools/x401/demo`](https://settled.tools/x401/demo) answers 401 with a `PROOF-REQUEST`, and a 0.1.0 `PROOF-REQUIRED`, asking for a Settled payer record that shows two facts. Retry with a valid `PROOF-RESPONSE` and it answers 200 with what it verified; send a bad one and it answers 401 with a `PROOF-RESULT` saying why. It counts no quota and Settled keeps nothing from the proof: Cloudflare's request records keep the IP address, the path and the user agent for up to 7 days, not the proof header. Its requests are unsigned and it accepts a key-binding `aud` of `origin:https://settled.tools` or `https://settled.tools`, with or without a trailing slash, so it suits clients that build the presentation themselves, as server-side agents do; it sends no CORS headers for browsers on other origins. Decoded, the request looks like this:

```
{
  "scheme": "x401",
  "version": "0.2.0",
  "credential_requirements": {
    "digital": {
      "requests": [{
        "protocol": "openid4vp-v1-unsigned",
        "data": {
          "response_type": "vp_token",
          "response_mode": "dc_api",
          "nonce": "x401:aHR0cHM6Ly9zZXR0bGVkLnRvb2xz:…",
          "exp": 1791932913,
          "dcql_query": { "credentials": [{
            "id": "payer_record",
            "format": "dc+sd-jwt",
            "meta": { "vct_values": ["https://settled.tools/vc/payer-record/v1"] },
            "claims": [
              { "path": ["active_days_30d_over_3"], "values": [true] },
              { "path": ["sanctions_clear"], "values": [true] }
            ]
          }] },
          "client_metadata": { "vp_formats_supported": { "dc+sd-jwt": {
            "sd-jwt_alg_values": ["ES256"], "kb-jwt_alg_values": ["ES256"] } } }
        }
      }]
    }
  },
  "oauth": {
    "token_endpoint": "https://settled.tools/x401/token",
    "audience": "https://settled.tools",
    "resource": "https://settled.tools/v1/free/endpoints"
  },
  "request_id": "settled-free-search-v1",
  "satisfied_requirements": ["urn:settled:x401:payer-record:free-search:v1"],
  "payment": { "required": false, "scheme_hint": "x402", "notes": "Or pay instead: a day pass …" }
}
```

The same request guards Settled's free tier: past 50 endpoint searches a day, the refusal carries it, and an agent that answers gets 150 searches a day for its wallet. Code for getting a record and answering, step by step: [Settled for agents](https://settled.tools/for-agents#payer-record). Every field, in plain text: [llms.txt](https://settled.tools/llms.txt).

## Payer records: what they prove and what they don't

A payer record is the credential Settled's verifier asks for, and one any x401 server can ask for. Settled signs it for a wallet's owner, as an SD-JWT VC, stating how that wallet has paid x402 sellers on Base over a 30-day window that ends shortly before it is issued (the record states the window), as Settled observed it. The holder shows each server only the facts that server asks for.

### It shows

- That the wallet paid independent sellers in Settled's index: on 3, 7, or 14 or more of the 30 days; $1, $10, or $100 or more in USDC; 5, or 10 or more, sellers. A seller is independent when 10 or more other wallets paid it in the window and it is not flagged as a clone farm, on hold or sanctioned.
- That the wallet was not on Settled's copy of the OFAC SDN list (refreshed daily) when the record was issued, and that no seller it paid has paid it back, as far as Settled's ledger of USDC payments to indexed sellers shows (it sees a payment back only when the wallet is itself an indexed seller's payTo address).
- The month of the first such payment Settled saw. Settled's history starts in September 2026.
- That whoever presents it holds the key the wallet's owner named when asking for it. The wallet itself signed that request.

### It does not show

- Who owns the wallet. There is no name, no country and no identity check: it is a record of payments, not an ID.
- Creditworthiness, a balance, or that the wallet will pay you.
- Payments to sellers outside Settled's index, in other tokens or on other chains, or exact amounts: each payment fact is a threshold.
- Anything that happened after it was issued, beyond what the status list shows. A record lasts 7 days. Settled revokes it overnight if the wallet turns up on the sanctions list or in a circular flow, and can revoke one by hand; the owner can revoke theirs at any time.

Every presentation reveals the wallet address, the network, the 30-day window, the holder's public key and the record's slot on the status list; only the facts are selective. So presentations of one record can be linked to each other, and the wallet is never hidden.

To check a record yourself: verify the ES256 issuer signature against the key at </.well-known/jwt-vc-issuer>, each disclosure's digest, the key-binding JWT (your nonce, your audience, signed by the key in `cnf`), the expiry, and the record's bit on the [status list](https://settled.tools/vc/status/1). SD-JWT VC libraries check the signature, the disclosures and the key binding (check `typ`, `iss`, `vct`, `sd_hash` and a recent `iat` too); the status list needs a Token Status List check. Or send the presentation to `POST https://settled.tools/v1/credentials/verify` with the `aud` and `nonce` you expect, and Settled checks it for you; without those two it cannot tell a presentation made for another verifier, or one replayed within 5 minutes. What each claim means: </vc/payer-record/v1>.

Records are free until January 11, 2027, then $0.10 for 7 days, paid by the agent over x402. Checking one is free.

## Accept payer records on your own API

Put a DCQL query like this in your own `PROOF-REQUEST`, naming the facts you care about:

```
{ "credentials": [{
  "id": "payer_record",
  "format": "dc+sd-jwt",
  "meta": { "vct_values": ["https://settled.tools/vc/payer-record/v1"] },
  "claims": [
    { "path": ["active_days_30d_over_7"], "values": [true] },
    { "path": ["sanctions_clear"], "values": [true] }
  ]
}] }
```

Then trust only records signed by the key at `https://settled.tools/.well-known/jwt-vc-issuer`, and check that the key-binding JWT's `nonce` is one you issued and that its `aud` names you the way OpenID4VP does for your request: over the Digital Credentials API, `origin:` followed by an invoking origin you accept (for a signed request, one of its `expected_origins`); for a relayed signed request, your `client_id`. A relayed unsigned request has no defined audience: rely on your nonce, and authenticate the agent if it matters.

## Settled's x401 error codes

x401 leaves error codes to each verifier. Settled's come in `PROOF-RESULT` (0.2.0) or `PROOF-RESPONSE` (0.1.0), with a fresh request alongside:

| Code | Meaning |
| --- | --- |
| `invalid_result` | The PROOF-RESPONSE is not a usable Result Artifact: not base64url JSON, over 22,000 characters, both or neither of credential\_result and credential\_result\_uri, a result by reference (Settled issues none, so it takes results inline only), not OpenID4VP, or an encrypted response. |
| `invalid_presentation` | The presentation is malformed or over 16 KB, or a 0.1.0 VP Artifact is malformed. |
| `challenge_mismatch` | The key-binding JWT's nonce is not one Settled issued; in 0.1.0, also an artifact challenge Settled did not issue or that differs from the nonce. |
| `challenge_expired` | The nonce is more than 5 minutes old. Take the one from a fresh PROOF-REQUEST. |
| `audience_mismatch` | The key-binding JWT's aud does not name Settled: origin:https://settled.tools or https://settled.tools in 0.2.0, your agent\_id in 0.1.0. |
| `key_binding_required` | No key-binding JWT is appended to the presentation. |
| `invalid_key_binding` | The key-binding JWT is malformed, not signed by the record's holder key, has the wrong sd\_hash, or its iat is more than 5 minutes off. |
| `invalid_disclosure` | A disclosure is not part of the record, or appears twice. |
| `untrusted_issuer, bad_signature` | Not a payer record signed by settled.tools. |
| `unknown_record` | Settled has no record of issuing this credential. |
| `expired, not_yet_valid` | Outside the record's 7 days. |
| `revoked` | The record's bit on the status list is set. |
| `requirement_not_met` | The presentation does not show active\_days\_30d\_over\_3 and sanctions\_clear as true. |
| `invalid_token` | The Bearer token is not valid or has expired (tokens last an hour). |
| `verifier_unavailable` | Settled could not check the proof just now; try again. |

At the token endpoint a failed proof comes back as OAuth `invalid_grant`, with the code in `error_description` and in `PROOF-RESULT` (`PROOF-RESPONSE` for 0.1.0), without a fresh request.

## Questions

### What is x401?

An open HTTP protocol, published by Proof in June 2026, that lets a server ask a caller for verifiable proof, such as an identity, an age, a membership or a payment history, before it serves a request. The server sends a PROOF-REQUEST header; the caller retries with a PROOF-RESPONSE header carrying a credential presentation.

### How is x401 different from x402?

x402 asks for payment: a 402 Payment Required response with a quote, and a retry with the payment. x401 asks for proof: a credential request, and a retry with the presentation. They use the same challenge-and-retry pattern and can both apply to one route: in the draft's main flow the agent proves first, then pays, sending the proof or its token along with the payment.

### Is x401 a finished standard?

No. It is a working draft, version 0.2.0 as of October 2026, published as open source under the Apache-2.0 license. Proof has said it will submit x401 to the FIDO Alliance's work on agent authentication.

### Which HTTP status code does x401 use?

None is required. 401 Unauthorized is typical, but the status code describes the whole response, so a server can send PROOF-REQUEST with 200 OK or any other code that fits.

### What credentials can a server ask for?

Anything a wallet can present over OpenID4VP and the Digital Credentials API, described with a DCQL query: SD-JWT VCs, such as a Settled payer record, ISO mdoc documents, and other formats the query names.

### Does x401 give AI agents their own identity?

Not by itself. Its design has an agent carry proof from the person or organization that authorized it. Binding a presentation to one particular agent is optional and done with separate mechanisms, such as HTTP Message Signatures, mutual TLS or DPoP.

### What changed in x401 0.2.0?

The headers were renamed (PROOF-REQUIRED became PROOF-REQUEST, PROOF-PRESENTATION became PROOF-RESPONSE, and errors moved from PROOF-RESPONSE to PROOF-RESULT), the server now writes the Digital Credentials API request itself, the agent answers with a Result Artifact, and the token exchange type became result\_artifact.

### Where can I test an x401 client?

Settled runs a live verifier at https://settled.tools/x401/demo. It answers with a PROOF-REQUEST asking for a Settled payer record and checks the PROOF-RESPONSE (draft 0.2.0) or PROOF-PRESENTATION (0.1.0) you send back, keeping nothing from the proof.

## Sources

- [x401 specification, draft 0.2.0 (Proof)](https://x401.proof.com/)
- [x401 specification, draft 0.1.0](https://x401.proof.com/spec/0.1.0/)
- [x401 source and changelog on GitHub (Apache-2.0)](https://github.com/proof/x401)
- [Introducing x401: Bringing Proof of Identity to the Web (Proof, June 2026)](https://www.proof.com/blog/introducing-x401)
- [Proof's x401 establishes an open protocol for AI agent identity and authorization (Help Net Security, June 26, 2026)](https://www.helpnetsecurity.com/2026/06/26/proofs-x401-establishes-an-open-protocol-for-ai-agent-identity-and-authorization/)
- [OpenID for Verifiable Presentations 1.0 (OpenID Foundation)](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html)
- [Digital Credentials API (W3C)](https://www.w3.org/TR/digital-credentials/)
- [SD-JWT-based Verifiable Credentials (IETF draft)](https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/)
- [Token Status List (IETF draft)](https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/)
- [x402, payments over HTTP 402](https://www.x402.org/)

The GitHub changelog and README still use some earlier names, such as `presentation_requirements` and VP Artifact; the published specification is authoritative, and this page follows it.

Settled is an independent index of x402 endpoints. It is not affiliated with Proof, the authors of x401. This page describes the public x401 drafts as of the date above; the specification at x401.proof.com is authoritative. Also as Markdown: </x401.md>.
