> ## Documentation Index
> Fetch the complete documentation index at: https://docs.unwall.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Send a Payment

> Use the unified /v1/pay endpoint to send payments via x402, USDC, or fiat.

# 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.

| Recipient           | Rail          | Use Case                                       |
| ------------------- | ------------- | ---------------------------------------------- |
| URL                 | x402 Protocol | Pay for API calls in USDC                      |
| `0x...` address     | USDC Transfer | Send USDC on-chain                             |
| Bank details object | Fiat ACH      | Convert 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.

<CodeGroup>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```python Python theme={null}
  import requests

  headers = {
      "Authorization": "Bearer aw_live_xxxxxxxxxxxx",
      "Content-Type": "application/json",
  }
  payload = {
      "recipient": "https://api.example.com/v1/data",
      "max_amount_usdc": 1000000,
      "description": "Fetch market data",
  }
  response = requests.post("https://api.unwall.xyz/v1/pay", json=payload, headers=headers)
  print(response.json())
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch("https://api.unwall.xyz/v1/pay", {
    method: "POST",
    headers: {
      Authorization: "Bearer aw_live_xxxxxxxxxxxx",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      recipient: "https://api.example.com/v1/data",
      max_amount_usdc: 1000000,
      description: "Fetch market data",
    }),
  });
  const result = await response.json();
  console.log(result);
  ```
</CodeGroup>

```json Response theme={null}
{
  "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\": [...]}"
  }
}
```

<Note>
  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.
</Note>

## USDC Transfer

When the recipient is an Ethereum address (`0x...`), Unwall sends USDC on Base chain via Bridge.xyz.

<CodeGroup>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```python Python theme={null}
  import requests

  headers = {
      "Authorization": "Bearer aw_live_xxxxxxxxxxxx",
      "Content-Type": "application/json",
  }
  payload = {
      "recipient": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
      "amount_usd": 50.00,
      "description": "Vendor payment",
  }
  response = requests.post("https://api.unwall.xyz/v1/pay", json=payload, headers=headers)
  print(response.json())
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch("https://api.unwall.xyz/v1/pay", {
    method: "POST",
    headers: {
      Authorization: "Bearer aw_live_xxxxxxxxxxxx",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      recipient: "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
      amount_usd: 50.0,
      description: "Vendor payment",
    }),
  });
  const result = await response.json();
  console.log(result);
  ```
</CodeGroup>

```json Response theme={null}
{
  "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.

<CodeGroup>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```python Python theme={null}
  import requests

  headers = {
      "Authorization": "Bearer aw_live_xxxxxxxxxxxx",
      "Content-Type": "application/json",
  }
  payload = {
      "recipient": {
          "name": "Acme Corp",
          "account_number": "123456789",
          "routing_number": "021000021",
          "email": "billing@acme.com",
      },
      "amount_usd": 250.00,
      "description": "Invoice #1234",
  }
  response = requests.post("https://api.unwall.xyz/v1/pay", json=payload, headers=headers)
  print(response.json())
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch("https://api.unwall.xyz/v1/pay", {
    method: "POST",
    headers: {
      Authorization: "Bearer aw_live_xxxxxxxxxxxx",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      recipient: {
        name: "Acme Corp",
        account_number: "123456789",
        routing_number: "021000021",
        email: "billing@acme.com",
      },
      amount_usd: 250.0,
      description: "Invoice #1234",
    }),
  });
  const result = await response.json();
  console.log(result);
  ```
</CodeGroup>

```json Response theme={null}
{
  "id": "tx_ghi789",
  "status": "processing",
  "rail": "fiat_ach",
  "amount": 25000,
  "fee": 375,
  "currency": "usd",
  "recipient_name": "Acme Corp",
  "estimated_arrival": "2-3 business days"
}
```

<Warning>
  ACH transfers are not instant. Expect 2-3 business days for settlement. Use the `GET /v1/transactions` endpoint to track status changes.
</Warning>

## 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.

```json theme={null}
{
  "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 Code | Meaning             | Example                                                                 |
| ----------- | ------------------- | ----------------------------------------------------------------------- |
| `400`       | Validation error    | Missing required fields, insufficient balance, invalid recipient format |
| `403`       | Permission denied   | Token lacks `pay` or `x402` permission, project is paused               |
| `429`       | Rate limit exceeded | More than 100 requests per minute                                       |
| `502`       | Upstream failure    | Bridge.xyz or x402 facilitator returned an error                        |

```json Example error response theme={null}
{
  "detail": "Insufficient balance. Available: 1000000, Required: 5000000"
}
```

<Tip>
  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.
</Tip>

## Required Permissions

| Rail          | Required Permission |
| ------------- | ------------------- |
| x402 Protocol | `x402`              |
| USDC Transfer | `pay`               |
| Fiat ACH      | `pay`               |
