Mandates & x402 payments
A mandate is a spending budget the user signs in their FluxA Wallet: a description, a currency, a limit and a time window. Once it's signed, the agent can pay inside it with no further prompts: x402 APIs, payouts, agent-card funding, AgentMarket topups. The wallet enforces the limit and the window.
1. fluxa-wallet mandates → reuse a signed mandate if one fits
2. fluxa-wallet mandate-create --desc ... --amount ...
3. user opens authorizationUrl and signs
4. fluxa-wallet mandate-status --id <mandateId> → wait for "signed"
5. fluxa-wallet x402 --mandate <mandateId> --payload <402 payload>
6. retry the API request with the returned payment headerPlan mandates around the whole task, not each call. Pay-per-call APIs cost very little, so one budget covering many calls (for example about 100) saves the user from approving every request. Always say what the budget is for and get the user's agreement first.
mandate-create
Creates a mandate draft and returns the URL where the user signs it.
fluxa-wallet mandate-create \
--desc "Spend up to 1 USDC on social-search API calls for 14 days" \
--amount 1000000 \
--seconds 1209600| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--desc | string | Yes | — | Plain-language description the user sees when signing |
--amount | integer, atomic units | Yes | — | Budget limit. USDC/XRP: 6 decimals (1000000 = 1). Credits: 2 decimals (500 = 5 credits). CARD_USD: cents |
--seconds | integer > 0 | No | 28800 (8 h) | How long the mandate stays valid |
--category | string | No | general | Free-form category tag |
--currency | string | No | USDC | USDC, XRP, FLUXA_MONETIZE_CREDITS, or CARD_USD (linked card, Beta) |
--currency is not case-sensitive and accepts these aliases: credits, fluxa-monetize-credits, fluxa-monetize-credit for Monetize Credits; card, card-usd for CARD_USD. The merchant flags (--merchant-name, --merchant-url, --merchant-country, --transaction-ref, --ext) work only with CARD_USD and are covered on Linked card.
{
"success": true,
"data": {
"status": "ok",
"mandateId": "mand_xxxxxxxxxxxxx",
"authorizationUrl": "https://wallet.fluxapay.xyz/onboard/intent?oid=...",
"expiresAt": "2026-10-27T18:00:00.000Z"
}
}Give the user the complete authorizationUrl as a clickable link and tell them they need to approve it. The mandate can't be used until it's signed.
| Error | What to do |
|---|---|
Missing required parameters: --desc, --amount | Supply both. Both are always required |
Amount must be a positive integer (atomic units) | No decimals: convert to atomic units first |
Unsupported currency: "..." | Use one of the listed currencies or aliases |
Seconds must be a positive integer | Pass a whole number of seconds |
Merchant flags are only valid with --currency CARD_USD | Drop the merchant flags, or set --currency CARD_USD |
mandate-status
Shows one mandate's status, limit and remaining budget.
fluxa-wallet mandate-status --id mand_xxxxxxxxxxxxx| Flag | Type | Required | Description |
|---|---|---|---|
--id | string | Yes | Mandate ID. Use --id, not --mandate |
{
"success": true,
"data": {
"status": "ok",
"mandate": {
"mandateId": "mand_xxxxxxxxxxxxx",
"status": "signed",
"naturalLanguage": "Spend up to 1 USDC on social-search API calls for 14 days",
"currency": "USDC",
"limitAmount": "1000000",
"spentAmount": "0",
"remainingAmount": "1000000",
"validFrom": "2026-10-27T10:00:00.000Z",
"validUntil": "2026-11-10T10:00:00.000Z"
}
}
}Output trimmed. The mandate is ready to use once mandate.status is signed. Until then it is awaiting the user's approval: send them the authorizationUrl again.
x402
Signs a payment for an HTTP 402 Payment Required response, using a signed mandate. x402 and x402-v3 are the same command and accept the same flags.
fluxa-wallet x402 --mandate mand_xxxxxxxxxxxxx --payload "$PAYLOAD"| Flag | Type | Required | Description |
|---|---|---|---|
--mandate | string | Yes | A signed mandate ID |
--payload | JSON string, or base64-encoded JSON | Yes | The complete 402 payment requirements, unmodified |
What to pass as --payload
The CLI supports both x402 payload formats and detects which one it has from the payload itself:
| The 402 response… | Pass as --payload | Retry header | Token field in output |
|---|---|---|---|
has a PAYMENT-REQUIRED header (x402 v2) | The header value as-is (base64) | PAYMENT-SIGNATURE | data.paymentPayloadB64 |
| has the requirements in the JSON body (x402 v1) | The full response body | X-Payment | data.xPaymentB64 |
Pass the whole payload, accepts array included. Don't pick out individual fields.
For a v1 payload, the CLI uses the accepts entry whose currency matches the mandate. So if an API accepts both USDC and credits, a USDC mandate pays in USDC. For a v2 payload, the wallet picks the asset.
v1 output:
{
"success": true,
"data": {
"status": "ok",
"xPaymentB64": "eyJ4NDAyVmVyc2lvbiI6...",
"paymentRecordId": 123,
"expiresAt": 1761500060
}
}v2 output:
{
"success": true,
"data": {
"status": "ok",
"paymentPayloadB64": "eyJ4NDAyVmVyc2lvbiI6...",
"paymentRecordId": 42,
"expiresAt": 1761500300
}
}End-to-end script
API_URL="https://api.example.com/paid-endpoint"
MANDATE_ID="mand_xxxxxxxxxxxxx"
RESP=$(curl -s -D /tmp/x402_headers -w "\n%{http_code}" "$API_URL")
CODE=$(echo "$RESP" | tail -n1)
BODY=$(echo "$RESP" | sed '$d')
if [ "$CODE" = "402" ]; then
PR=$(grep -i '^payment-required:' /tmp/x402_headers | sed 's/^[^:]*: *//' | tr -d '\r')
if [ -n "$PR" ]; then
PAYLOAD="$PR"; HEADER="PAYMENT-SIGNATURE"; FIELD="paymentPayloadB64"
else
PAYLOAD="$BODY"; HEADER="X-Payment"; FIELD="xPaymentB64"
fi
TOKEN=$(fluxa-wallet x402 --mandate "$MANDATE_ID" --payload "$PAYLOAD" | jq -r ".data.$FIELD")
curl -s -H "$HEADER: $TOKEN" "$API_URL"
fiErrors
| Error | What to do |
|---|---|
Missing required parameters: --mandate, --payload | Create and sign a mandate first, then pass both flags |
Invalid --payload: not valid JSON or base64-encoded JSON | Pass the body or header exactly as received |
Invalid payload: missing accepts array | Pass the complete payload, not a single accepts entry |
Invalid v2 payload: missing resource.url | The v2 payload is incomplete. Pass the full PAYMENT-REQUIRED value |
No accepts entry matches mandate currency "..." | The API doesn't accept the mandate's currency. Create a mandate in one of the listed currencies |
mandate_not_signed | The mandate is awaiting approval. Send the user the authorizationUrl |
mandate_expired | The time window has passed. Create a new mandate |
mandate_budget_exceeded / mandate_insufficient_budget | Not enough budget left for this payment. Create a new mandate with a higher limit |
mandate_not_found | Wrong ID, or the mandate belongs to a different agent. Mandates belong to the agent that created them |
See also: x402 Payment guide.
