x402
Pay a 402 challenge behind a warrant, with a ceiling checked before anything is held.
POST /v1/x402/pay fetches a URL, and if it answers 402 it opens a warrant for the quoted amount, retries the request, and settles on whether the paid response actually passed your spec.
{
"url": "https://api.exampledata.io/invoices/8812",
"method": "GET",
"max_amount": "10000000",
"spec": { "clauses": [{ "kind": "http.status", "equals": 200 }] }
}What it does, in order
- Requests the URL. If it answers anything other than 402, you get that response and nothing is held.
- Parses the x402 challenge — top level or nested under
x402, since servers differ. - Refuses if the quoted amount exceeds
max_amount. Checked before the hold, because the point of a cap is that it is checked before the commitment. - Opens a warrant for the quoted amount.
- Retries the request with payment.
- Runs your spec against the paid response and lets the policy decide.
If the paid retry cannot be reached, the hold is refunded and you are told so.
Request
| Field | Type | Required | Notes |
|---|---|---|---|
url | string | yes | Must be a URL. |
method | GET or POST | no | Defaults to GET. |
headers | object | no | String to string. |
max_amount | string | yes | Smallest unit, ^\d+$. The ceiling. |
spec.clauses | array | yes | At least one. |
agent_id | string | no | Attaches the spend session. |
Needs the operator role or any API key, plus an
Idempotency-Key. Opening a warrant is what a key
is for.
POST /v1/x402/pay HTTP/1.1
Host: api.gowarrant.xyz
Authorization: Bearer wr_test_8f0c1b4e2b1a4d599d0e3a2f7c1b9e44
Idempotency-Key: 0d8f1a1e-6f2c-4c1a-9a3f-2b6e5d4c3b2a
Content-Type: application/json
{
"url": "https://api.exampledata.io/invoices/8812",
"method": "GET",
"max_amount": "10000000",
"spec": {
"clauses": [
{ "kind": "http.status", "equals": 200 },
{ "kind": "data.freshness", "maxAgeSeconds": 60 }
]
}
}200
{
"warrant_id": "wrt_01J8Z3M4YQ7K2V9XABCDEFGHJK",
"state": "released",
"quoted": {
"amount": "8400000",
"asset": "USDC",
"payee": "api.exampledata.io"
},
"paid": true,
"released_bps": null,
"passed": true,
"reason_codes": [],
"status": 200,
"body": "{\"invoice\":\"8812\",\"total\":\"84.00\"}"
}curl -sS https://api.gowarrant.xyz/v1/x402/pay \
-H "Authorization: Bearer $WARRANT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.exampledata.io/invoices/8812",
"method": "GET",
"max_amount": "10000000",
"spec": {
"clauses": [
{ "kind": "http.status", "equals": 200 },
{ "kind": "data.freshness", "maxAgeSeconds": 60 }
]
}
}'paid and passed are different questions
paid means the payee actually received something — the warrant is released
or partially_released. passed means the check was a clean pass.
They come apart on a partial release: paid is true, passed is false, and
released_bps says how much moved. Reading paid as "the request succeeded"
is the mistake this response shape is designed to prevent.
state carries the warrant's actual state, and escalated is a legitimate
answer — the response returns while a person still has to decide.
Errors specific to this route
code | Status | Cause |
|---|---|---|
upstream_unreachable | 400 | The first request failed. Nothing was held. |
insufficient_funds | 402 | The quote exceeds available balance. |
A quote above max_amount is refused before any hold exists, so a server that
raises its price cannot drain you by answering a bigger number.
Facilitator mode
Not built
Warrant acting as an x402 facilitator — settling on behalf of other parties rather than paying as itself — is not implemented. There is no facilitator endpoint, no settlement callback and no registration flow.
What exists is the client side above: Warrant pays an x402 challenge on your behalf and holds the money until the response is checked.
Amounts
max_amount and the quoted amount are integer strings of the asset's smallest
unit, ^[0-9]+$.
"10000000" 10.00 USDC
10000000 rejected — a JSON number
"10.00" rejected — not the smallest unit