Payouts
A payout sends USDC on Base from the user's FluxA Wallet to an EVM address. By default the user approves each payout in FluxA Wallet. If you pass a signed mandateId with enough budget, the payout is approved automatically and needs no user action.
All endpoints require Authorization: Bearer <agent-jwt>.
Create payout
POST/api/payoutsBearer agent-jwtcurl -X POST https://walletapi.fluxapay.xyz/api/payouts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AGENT_JWT" \
-d '{
"payoutId": "po_abc123",
"toAddress": "0x1234567890abcdef1234567890abcdef12345678",
"amount": "1000000",
"mandateId": "mand_xxxxxxxxxxxx",
"bizId": "order_20260416_001",
"description": "Refund for order #001"
}'Request fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
payoutId | string | Yes | – | Your idempotency key (max 128 chars). A repeat request with the same payoutId and the same parameters returns the existing payout. |
toAddress | string | Yes | – | Destination EVM address (0x + 40 hex chars). The zero address is rejected. |
amount | string | Yes | – | Positive integer string in atomic units (1000000 = 1.0 USDC). |
mandateId | string | No | – | A signed USDC mandate belonging to this agent. If it is active and has enough budget, the payout is approved automatically. |
bizId | string | No | – | Your business reference. Only one live payout per bizId is allowed (see bizId deduplication). |
network | string | No | base | base or base-sepolia (CAIP-2 eip155:8453 / eip155:84532 are also accepted). |
currency | string | No | USDC | Only USDC is supported. |
assetAddress | string | No | USDC on the chosen network | If you send it, it must be the USDC contract for that network (Base: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913). |
description | string | No | – | Memo shown to the user (max 500 characters). |
metadata | object | No | – | Your own JSON metadata. |
ttlSeconds | integer | No | 604800 | How long the user has to approve the payout, from 60 to 604800 seconds (7 days). |
Response
201 Created for a new payout. 200 OK for an idempotent repeat with the same payoutId and parameters.
{
"payoutId": "po_abc123",
"txHash": null,
"approvalUrl": "https://agentwallet.fluxapay.xyz/authorize-payout/po_abc123",
"expiresAt": 1713340800,
"mandateId": "mand_xxxxxxxxxxxx",
"bizId": "order_20260416_001",
"description": "Refund for order #001"
}| Field | Description |
|---|---|
payoutId | Your payout ID. |
txHash | On-chain transaction hash once the transfer is sent; null before that. |
approvalUrl | FluxA Wallet page for this payout. Without a mandate, give this link to the user so they can approve or reject the payout. |
expiresAt | Approval deadline (Unix seconds). |
mandateId | The mandate used for automatic approval, or null. |
bizId | Your business reference, or null. |
description | The memo, or null. |
To track the result, poll Get payout status and read displayStatus.
Error responses
Error bodies have an error message and, for most cases, a machine-readable code.
| HTTP | code | When |
|---|---|---|
| 400 | – | A required field is missing or invalid (payoutId, toAddress, amount, bizId, mandateId, assetAddress). |
| 400 | unsupported_currency / unsupported_network | The currency or network isn't supported. |
| 400 | invalid_ttl_seconds | ttlSeconds is outside 60–604800. |
| 400 | invalid_description / description_too_long | description isn't a string or is longer than 500 characters. |
| 400 | mandate_not_signed | The user hasn't signed the mandate yet. |
| 400 | mandate_disabled | The user disabled the mandate. |
| 400 | mandate_expired | The mandate is outside its validity window. |
| 400 | mandate_currency_mismatch | The mandate isn't a USDC mandate. |
| 400 | mandate_insufficient_budget | The mandate's remaining budget is too small. The body includes remaining (atomic units). |
| 401 | – | The agent JWT is missing, invalid or expired. A VC token returns { "error": "wrong_token_type" }. |
| 403 | mandate_mismatch | The mandate belongs to a different agent. |
| 404 | mandate_not_found | The mandate doesn't exist. |
| 404 | – | The agent isn't linked to a FluxA Wallet yet. |
| 409 | – | The payoutId was already used with different parameters. The body includes the existing payout. |
| 409 | – | The bizId is already in use. The body includes existingPayoutId. |
Automatic approval with a mandate
When you send a mandateId, FluxA checks that the mandate:
- belongs to this agent,
- is signed and enabled,
- is inside its validity window,
- is a
USDCmandate, and - has a remaining budget of at least
amount.
If all of these pass, the payout skips user approval and starts processing right away. The amount counts against the mandate's budget. If any check fails, the request is rejected with one of the mandate_* codes above and no payout is created.
bizId deduplication
bizId stops your system from paying the same business item twice. Each user can have only one live payout per bizId:
- If a payout with the same
bizIdis awaiting approval, processing, completed or expired, a new payout with thatbizIdreturns409withexistingPayoutId. - After a payout has failed or been rejected, you can create a new payout with the same
bizId.
payoutId and bizId together
payoutIdmakes a single request safe to retry. The samepayoutIdwith the same parameters returns the existing payout (200). The samepayoutIdwith different parameters, including a differentbizId, returns409.bizIddeduplicates at the business level. A differentpayoutIdwith a livebizIdreturns409.- The two are independent. You can use either, both or neither.
Get payout status
GET/api/payouts/:payoutIdBearer agent-jwtSee Get payout status.
