Payments (x402) & settlement
The protocol never holds funds. What it holds is a reservation against a mandate's cap, bound amount-exact and single-use to one authorizationId — so a payment cannot be authorized for $50 and settled for $5,000, and a hold cannot be spent twice.
Authorize before pay, always
A permitting verdict on a value-bearing action atomically reserves the amount against the mandate's cap before anything is spent. A payment whose amount differs from what was authorized is refused with AMOUNT_MISMATCH; a reused authorization is refused outright. This is the check that stops "authorize $250, execute $5,000" — the exact attack the public sandbox demo runs live.
Two-phase: hold, then capture or void
- POST /policy/mandate/authorize/:id/effect/dispatching — a counterparty claims the hold for execution; the reservation now stays committed until it's settled or proven not to have happened, even past the hold's own TTL
- POST /policy/mandate/authorize/:id/capture — settle with the real amount and a settlement reference; a hold nobody claimed simply expires and auto-voids
- A settled amount BELOW what was authorized needs the claim token returned by that hold's successful claim (data.claimToken) — capture and void are public, keyed only by the authorization id, so an untrusted party settling low without the token would otherwise be believed
- GET /policy/mandate/authorize/:id/effect — outcome lookup: not_started, in_flight, settled, unknown, reversed... with nothingExecuted and retrySafe flags that say whether it's safe to issue a fresh authorization for the same intent
Capability binding — authorize A, execute B
A separate check from the amount: COMMITMENT_MISMATCH fires when the executed transaction doesn't match what was actually authorized (the merchant, the route, the bound resource) even if the amount happens to line up. Both checks exist because an amount match alone doesn't prove the same action ran.
See it in code
# After a permitting verdict, decision.authorizationId is the hold.
curl -X POST https://metamynd.ai/api/v1/policy/mandate/authorize/$AUTH_ID/capture \
-H "Content-Type: application/json" \
-d '{ "amountCharged": 150, "settlementTxHash": "0xabc..." }'
# Settling BELOW the authorized amount, or voiding a CLAIMED hold, needs the
# claim token from that hold's own claim response — never the agent's own token:
curl -X POST https://metamynd.ai/api/v1/policy/mandate/authorize/$AUTH_ID/capture \
-H "Content-Type: application/json" \
-d '{ "amountCharged": 120, "claimToken": "'"$CLAIM_TOKEN"'" }'