Skip to main content
An x402 seller is discovered at request time: its address arrives in a 402 response, not from a list your customer approved. And the risk is rarely one large payment; it is thousands of small ones adding up. So an x402 mandate authorizes a budget rather than a payee: a per-call ceiling, a rolling total across every seller, and a policy on who may be paid.

The x402 mandate

POST /payment-agents/{payment_agent_id}/x402/mandates Amounts are atomic units: USDC has 6 decimals, so "1000000" is $1.00. GET /payment-agents/{payment_agent_id}/x402/mandates lists the agent’s mandates, newest first and paged like the holds list (limit, starting_after, ending_before, answering data plus meta). Each shows the current window’s window_committed (atomic units) and window_calls.

Which mandate applies

A signature request is checked against the agent’s newest mandate that is currently valid and covers the payment’s asset and network. Older mandates for the same asset and network are not consulted, and each mandate counts its own window. A new mandate therefore starts with an empty window.

Payee policies

Every payee is screened before anything is signed, whatever the mode — a refused address is a 403 and no budget is held. any_screened means screening is the only payee control; the allowlists add to it rather than replacing it. Omit payee_policy entirely to get any_screened. If you send it, mode is required and the list must match it — addresses only with address_allowlist, domains only with domain_allowlist, and neither with any_screened. Anything else is a 400, so a policy is never stored looking like a restriction that isn’t applied. domain_allowlist is the readable form: a person can approve “any metered API under marketpulse.example” in a way they can’t approve a hex address. It judges the host of the resource_url you send with each signature request, so resource_url is required under this policy. When the seller’s terms name their resource, the two must match.
x402 v2 sellers name their resource outside the payment terms, so under domain_allowlist Dakota checks the resource_url you supply without a second source to compare it against. If you need a limit that doesn’t depend on the calling application, use address_allowlist.

Holds

Every signature Dakota issues is recorded as a hold before the signature exists. The hold is what the budget counts, and it’s the only record of x402 spend: the seller’s facilitator moves the money, so there is no Dakota wallet transaction. outstanding counts because an issued signature is spendable money whether or not it has reached the chain yet. The budget never assumes a payment didn’t happen. GET /payment-agents/{payment_agent_id}/x402/holds lists the agent’s holds, newest first, in pages: limit, starting_after and ending_before, with the rows in data and a meta object carrying total_count, has_more_after and has_more_before. Filter with ?state=outstanding, settled or released. Reading holds needs both ReadMandates and ReadTransactions: they are the only record of what an agent paid over x402.

How holds resolve

Dakota reads the USDC contract directly rather than waiting to be told:
  • Settled: usually within about a minute of the seller’s facilitator submitting the transfer. The tx_hash is the transaction the token contract recorded.
  • Released: a few minutes after valid_before, once a Base block that can no longer be reorganized away (at least two minutes past valid_before) shows the authorization still unused. From that point the token refuses it for good.
  • Anything uncertain stays outstanding, and so stays counted, until the chain gives a definite answer.

How long a signature is valid

valid_before is the earliest of:
  • the seller’s maxTimeoutSeconds from now
  • 10 minutes from now
  • the mandate’s valid_until
So no signature outlives the mandate that allowed it.

Refusals

A refused payment is never signed, and it doesn’t count against the budget. Every 403 carries the specific reason in detail, for example payment of 700000 exceeds the per-call limit of 500000.