title: Payments description: "Send USDC; query payment status."
Base URL: https://api.cardzero.ai/v1
Send payment
POST /paymentsAPI Key required. Rate-limited at 60/min per API Key.
Body:
{
"to": "0xRecipient…",
"amount": "1.0",
"currency": "USDC",
"memo": "optional note",
"idempotencyKey": "optional-uuid",
"type": "direct"
}
| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| to | string | yes | EVM address, 0x-prefixed, 40 hex chars |
| amount | string | yes | Decimal USDC, max 6 decimals (e.g. "1.5", "0.001") |
| currency | string | yes | Must be "USDC" |
| memo | string | no | Free-form, ≤ 200 chars |
| idempotencyKey | string | no | Use to prevent duplicate charges on retry |
| type | "direct" | "x402" | no | Default "direct"; "x402" tags the payment for filtering |
Response 200:
{
"id": "pay_abc123def456",
"txHash": "0x…",
"status": "confirmed",
"amount": "1.0",
"feeAmount": "0.02",
"to": "0xRecipient…",
"type": "direct"
}
status values: pending, confirmed, failed.
The recipient gets the full amount. The wallet is debited
amount + feeAmount (e.g. wallet -1.02 USDC, recipient +1.0 USDC, treasury +0.02 USDC).
Get payment
GET /payments/:paymentIdNo authentication. Payment IDs are unguessable (random 12-char hex).
Response 200:
{
"id": "pay_abc123def456",
"wallet_id": "wallet_…",
"to_address": "0x…",
"amount": "1.0",
"type": "direct",
"memo": "optional",
"status": "confirmed",
"tx_hash": "0x…",
"fee_amount": "0.02",
"fee_recipient": "0x41a4…91da",
"created_at": 1715000000
}
List payments for wallet
GET /wallets/:id/paymentsJWT or API Key. Pagination via ?limit + ?offset.
Query params:
| Param | Default | Max |
| --- | --- | --- |
| limit | 20 | 100 |
| offset | 0 | — |
Response 200:
{
"payments": [ /* same shape as Get payment */ ]
}
Errors
| Code | HTTP | When |
| --- | --- | --- |
| UNSUPPORTED_CURRENCY | 400 | currency != "USDC" |
| INVALID_AMOUNT | 400 | amount format invalid or ≤ 0 |
| INVALID_ADDRESS | 400 | recipient address malformed |
| WALLET_BUSY | 409 | another payment in progress for same wallet |
| INSUFFICIENT_BALANCE | 400 | wallet doesn't have enough USDC |
| WALLET_FROZEN | 400 | wallet is frozen |
| TX_LIMIT_EXCEEDED | 400 | amount exceeds per-tx limit |
| DAILY_LIMIT_EXCEEDED | 400 | sum of today's payments would exceed daily limit |
| NOT_IN_WHITELIST | 400 | recipient not in whitelist (when whitelist is non-empty) |
| WALLET_EXPIRED | 400 | past wallet's expiresAt |
| INVALID_API_KEY | 401 | API key not recognized |
Idempotency: if you send the same idempotencyKey twice for the same wallet,
the second call returns the first payment's record (no double-charge).