# x402

URL: https://docs.hanzo.ai/docs/openapi/x402

> Pay-per-request over HTTP 402: quote a price, take the payment, serve the resource.

Also for this capability: **API** · [CLI](https://docs.hanzo.ai/docs/cli/x402) · [SDKs](https://docs.hanzo.ai/docs/sdks)

Pay-per-request over HTTP 402: quote a price, take the payment, serve the resource.

> [All API references →](https://docs.hanzo.ai/docs/openapi) · [Get started →](https://docs.hanzo.ai/docs/guides)

|                |                                        |
| -------------- | -------------------------------------- |
| **Base URL**   | `https://api.hanzo.ai`                 |
| **Operations** | 2                                      |
| **Auth**       | `Authorization: Bearer $HANZO_API_KEY` |

## Specification

> **HIP-1163 · x402 — Pay Per Request** — Draft · [read the specification →](https://github.com/hanzoai/hips/blob/7ed737180339cd05b5f2935eafe7f7b83d2449a2/HIPs/hip-1163-x402-pay-per-request.md)

x402 is pay-per-request over HTTP 402: quote a price, take the payment, serve
the resource. It speaks x402 protocol version 2 — challenge, EIP-3009
`transferWithAuthorization` signed over the advertised terms, verify, settle
exactly once, serve — native to the cloud binary. It is a rail and nothing
else: the marketplace declares what is priced and who is paid, wallets says
where the money lands, and this capability enforces. The implementation is
`hanzoai/cloud` `apps/x402`.

### Motivation

A marketplace where agents buy tools and data per call needs a payment that
works without an account relationship: the buyer may be a client that has
never heard of the seller, and the price may be a fraction of a cent. HTTP 402
with a signed transfer authorization is the shape that fits — the challenge
carries the terms, the retry carries the proof, and no session, invoice or
card vault sits between them. What it demands in exchange is exactness:
replay, double-settlement and misdirected credit are all one-request attacks
if the rail is loose anywhere.

### Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted
as in RFC 2119.

#### §1 The wire is the spec's, not ours

The protocol is x402 version 2 as specified by `x402-foundation/x402`
(`specs/x402-specification-v2.md` and its HTTP transport binding). Every field
name, header name and error reason on the wire MUST be the one the
specification prints (`apps/x402/protocol.go`): the wire is a contract with
other people's clients, and a client that has never heard of Hanzo must be
able to pay us. The version is a number on the wire (`x402Version: 2`) and the
only thing that decides how a message is read — a wire whose shape depends on
an operator flag is two wires. One scheme (`exact`), one transfer method
(EIP-3009); signature verification is secp256k1 recovery over the EIP-712
domain the challenge itself stated. The default settlement network is
`eip155:36963`, the Hanzo L1, matching wallets.

#### §2 The store, and settle-once

One SQLite store, `x402` (`apps/x402/store.go:55`): one row per claimed
authorization, keyed by a deterministic id derived from (payer address,
nonce). That id is both the replay-dedup key — a primary key, so a second
insert of the same authorization is atomically refused — and the idempotency
ref on both money writes, so the fast in-pod dedup and the ledger's own
idempotency agree on one key. A row is written at claim and flipped at settle;
an interrupted settlement is recoverable because the claim names payer, payee,
amount and the payee's ledger subject, and startup `Reconcile` completes
anything the last process left in doubt (`apps/x402/x402.go:181`). The payee
subject is on the row and MUST NOT be re-resolved at completion time — a claim
has to be sufficient on its own, or the sweep silently pays whatever the
wallet resolves to later.

#### §3 The address, and the three callers

The addressed surface is one typed operation, `GET
/v1/x402/settlements/&#123;id&#125;` (`manifest/apps.go:174`) — the receipt lookup,
scoped by `principal.Ledger`, which folds in the SuperAdmin masquerade so an
admin inspecting another org still reads their own settlements
(`apps/x402/x402.go:791`). The flow itself is written once (`run`) and reached
three ways: `Enforce`, a handler a priced route group applies; `Settle`, the
same flow for a resource named in a request body; and the plane op
`/x402/settle` for the fleet's one-binary-per-app shape, where the payer is
the caller's org resolved at the edge and never a field
(`apps/x402/rpc.go`). On the plane the outcome is data, not a transport error
— a 402 carries the terms the client must read to pay — so a transport error
means the rail did not answer, and callers fail closed on it, never free.

#### §4 Money

The surface declares `cloud.Free` (`plugin/x402/main.go`): the rail itself
charges nothing per request. The money it moves is the settlement — a payer
debit through the metering spine under provider label `x402` with the
settlement id as `Ref` (`apps/x402/x402.go:732`), which is how paid usage
appears in billing/usage like any metered spend, and a payee credit deposited
into the ledger subject of the wallet the publisher's listing named, resolved
through wallets (HIP-1161). What a resource costs is the marketplace
registry's table; when the price cannot be learned the answer MUST be an
error, never "free" — a rail that reads its own outage as "nothing is priced"
sells the whole catalogue for nothing (`apps/x402/peer.go`). Unknown payee is
503; unreachable ledger is no settlement, no receipt, no dispatch.

#### §5 Events and telemetry

It publishes nothing on the bus; a customer's webhooks receive no `x402.*`
events. Beyond the request span, each settlement appends an audit record
(resource type `x402.settlement`, redacted; `apps/x402/x402.go:899`), and the
startup reconcile logs what it completed.

#### §6 Upstream

It implements the x402 specification version 2 (`x402-foundation/x402`) — a
wire specification, not a code fork; no upstream implementation is embedded.
Signature recovery rides `github.com/luxfi/crypto` v1.20.5 (Lux Ecosystem
License 1.2), the same primitive wallets custody signs with. Persistence is
`github.com/hanzoai/sqlite` (MIT / Apache-2.0 dual).

#### §7 Stage

`beta`: the manifest row declares it (`manifest/apps.go:174`, `Stage: Beta`;
HIP-0139 §8), reached by flag until promoted.

### Rationale

The alternative to a rail-plus-registry seam is a payments module inside each
priced subsystem. That is N copies of verify-and-settle, N nonce stores that
do not share a dedup key, and a marketplace that cannot price a resource
without shipping code into its owner. One rail, inert until a registry is
published, keeps enforcement in one place and pricing where the catalogue is.

### Security Considerations

The attacks are the classic payment ones, and each is closed structurally.
Replay: the (payer, nonce) primary key refuses a second claim atomically, and
the same id is the ledger's idempotency key, so even a bug in the fast path
cannot double-move money. Redirection: the payee org comes off the listing
row and the payer off the edge-resolved principal — neither is a request
field, so a buyer can neither choose who is paid nor who pays. Free-fall: the
priced set must fail closed (§4); the one exception this rail ever held —
reading an unreachable marketplace as "no marketplace, nothing priced" — is
recorded in `apps/x402/peer.go` as the defect that sold everything for
nothing, and is why the price has no exception now.

## Four surfaces

| Surface  | Reaches this capability as                   | Coverage                                                                  |
| -------- | -------------------------------------------- | ------------------------------------------------------------------------- |
| **REST** | `x402` at its own prefix                     | 2 operations                                                              |
| **CLI**  | `hanzo x402 …`                               | 1 of 2 — the CLI pins the document on its own clock                       |
| **SDK**  | `X402Api` in every published client          | 2 methods                                                                 |
| **MCP**  | tool `x402` on `https://api.hanzo.ai/v1/mcp` | 2 operations, 0 under the document's own id — ask `describe` for the rest |

## Quickstart

```bash
export HANZO_API_KEY=sk-...   # console.hanzo.ai → API keys
```

Then the first call — a read that needs nothing but the key. `GET /v1/x402/settlements`, operation `get_x402_settlements`:

**CLI**

`hanzo` has no subcommand for this operation — the CLI serves only what cloud's live route table confirms. Use HTTP or an SDK.

**SDK**

**.ts**

```ts
import { Configuration, X402Api } from 'hanzoai';

const api = new X402Api(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.getX402Settlements();
```

**.py**

```python
from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import X402Api

client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = X402Api(client).get_x402_settlements()
```

**.go**

```go
cfg := hanzoai.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := hanzoai.NewAPIClient(cfg)

resp, _, err := client.X402API.GetX402Settlements(context.Background()).Execute()
if err != nil {
	return err
}
```

**.rs**

```rust
use hanzo_client::apis::{configuration::Configuration, x402_api};

let mut cfg = Configuration::new();
cfg.bearer_access_token = std::env::var("HANZO_API_KEY").ok();

let result = x402_api::get_x402_settlements(&cfg, Default::default()).await?;
```

**.java**

```java
import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.X402Api;

ApiClient client = new ApiClient();
client.setBearerToken(System.getenv("HANZO_API_KEY"));

var result = new X402Api(client).getX402Settlements();
```

**HTTP**

```bash
curl https://api.hanzo.ai/v1/x402/settlements \
  -H "Authorization: Bearer $HANZO_API_KEY"
```

**MCP**

MCP reaches **x402** through the `x402` tool, which names its 2 operations with its own verbs — this one among them, under a name only MCP declares. `describe` explains any of them:

```bash
curl -X POST https://api.hanzo.ai/v1/mcp \
  -H "Content-Type: application/json" \
  -d '{
       "jsonrpc": "2.0",
       "id": 1,
       "method": "tools/call",
       "params": {
         "name": "describe",
         "arguments": {
           "op": "list_x402_settlements"
         }
       }
     }'
```

Answers `200` with `object` — ok.

## Endpoints

| Endpoint                                                                                              | What it does                                                                                                                                                                                    |
| ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`GET /v1/x402/settlements/{id}`](https://docs.hanzo.ai/docs/openapi/x402/get-x402-settlements-by-id) | Reads one x402 payment receipt by id.                                                                                                                                                           |
| [`GET /v1/x402/settlements`](https://docs.hanzo.ai/docs/openapi/x402/get-x402-settlements)            | Lists the caller's x402 receipts, newest first: as payer, what its ledger paid; as payee, what it was paid — each settled payment with what it bought, both parties, the exact amount and when. |

---

[All Hanzo APIs](https://docs.hanzo.ai/docs/openapi) · [Interactive reference](https://docs.hanzo.ai/docs/openapi/reference)
