Skip to main content

Send a Payment

The POST /v1/pay endpoint is Unwall’s unified payment interface. It accepts three types of recipients and automatically routes to the correct payment rail — no rail selection logic needed in your agent code.
RecipientRailUse Case
URLx402 ProtocolPay for API calls in USDC
0x... addressUSDC TransferSend USDC on-chain
Bank details objectFiat ACHConvert USDC to USD and send via bank transfer

x402 Payment

When the recipient is a URL, Unwall proxies the request and handles the x402 payment protocol automatically.
curl -X POST https://api.unwall.xyz/v1/pay \
  -H "Authorization: Bearer aw_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "recipient": "https://api.example.com/v1/data",
    "max_amount_usdc": 1000000,
    "description": "Fetch market data"
  }'
Response
{
  "id": "tx_abc123",
  "status": "completed",
  "rail": "x402",
  "amount_charged": 50000,
  "fee": 1000,
  "currency": "usdc",
  "recipient": "https://api.example.com/v1/data",
  "tx_hash": "0xabc...",
  "response": {
    "status_code": 200,
    "body": "{\"data\": [...]}"
  }
}
The max_amount_usdc field is in micro-USDC (1 USDC = 1,000,000). Setting it to 1000000 means the agent will pay up to 1 USDC for this call. If the API charges more, the payment is rejected.

USDC Transfer

When the recipient is an Ethereum address (0x...), Unwall sends USDC on Base chain via Bridge.xyz.
curl -X POST https://api.unwall.xyz/v1/pay \
  -H "Authorization: Bearer aw_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "recipient": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
    "amount_usd": 50.00,
    "description": "Vendor payment"
  }'
Response
{
  "id": "tx_def456",
  "status": "processing",
  "rail": "usdc_transfer",
  "amount": 50000000,
  "fee": 750000,
  "currency": "usdc",
  "recipient": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
  "tx_hash": "0xdef..."
}

Fiat ACH Payment

When the recipient is an object with bank details, Unwall converts USDC to USD and sends via ACH bank transfer.
curl -X POST https://api.unwall.xyz/v1/pay \
  -H "Authorization: Bearer aw_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "recipient": {
      "name": "Acme Corp",
      "account_number": "123456789",
      "routing_number": "021000021",
      "email": "billing@acme.com"
    },
    "amount_usd": 250.00,
    "description": "Invoice #1234"
  }'
Response
{
  "id": "tx_ghi789",
  "status": "processing",
  "rail": "fiat_ach",
  "amount": 25000,
  "fee": 375,
  "currency": "usd",
  "recipient_name": "Acme Corp",
  "estimated_arrival": "2-3 business days"
}
ACH transfers are not instant. Expect 2-3 business days for settlement. Use the GET /v1/transactions endpoint to track status changes.

Idempotency

Include an idempotency_key to prevent duplicate payments. If a request is retried with the same key, the original transaction is returned without creating a new one.
{
  "recipient": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
  "amount_usd": 50.00,
  "idempotency_key": "invoice-1234-payment",
  "description": "Vendor payment"
}
This is especially important for AI agents that may retry requests on network errors or timeouts. Idempotency keys are scoped to each project — the same key can be used in different projects without conflict.

Error Handling

Status CodeMeaningExample
400Validation errorMissing required fields, insufficient balance, invalid recipient format
403Permission deniedToken lacks pay or x402 permission, project is paused
429Rate limit exceededMore than 100 requests per minute
502Upstream failureBridge.xyz or x402 facilitator returned an error
Example error response
{
  "detail": "Insufficient balance. Available: 1000000, Required: 5000000"
}
Always check the status field in the response. A 200 response with "status": "processing" means the payment was accepted but has not yet settled. Use GET /v1/transactions or wait for webhook confirmation to verify completion.

Required Permissions

RailRequired Permission
x402 Protocolx402
USDC Transferpay
Fiat ACHpay