Reference
Errors & limits.
Errors share one envelope. Branch on the HTTP status and the stable code, never on the human-readable message.
The error envelope
| Field | Meaning |
|---|---|
type | A broad category, like authentication_error, invalid_request_error, not_found_error or api_error. |
code | A stable machine-readable identifier. Safe to switch on. |
message | A human-readable explanation. Wording can change. |
param | For validation errors, the field that caused it. |
request_id | Also sent as the X-Request-Id response header. Include it when contacting support. |
A malformed body returns 400 validation_failed, with param naming the first offending field. Unexpected server failures return 500 internal_error and are captured for investigation.
Common codes
| Status | code | Meaning |
|---|---|---|
| 400 | validation_failed | The request body or query failed validation. |
| 401 | missing_api_key | No API key, or the header isn't Bearer sk_… |
| 401 | invalid_api_key | The key doesn't exist or was revoked. |
| 404 | transaction_not_found | The payment doesn't exist or belongs to another merchant. |
| 404 | payment_link_not_found | The checkout link doesn't exist. |
| 400 | payment_link_expired | The link's expiry has passed. |
| 400 | merchant_wallet_not_connected | The merchant hasn't connected a payout wallet. |
| 400 | unsupported_wallet_currency | payCurrency isn't one of the supported assets for this network. |
| 400 | custom_amount_required | The link takes a customer-chosen amount but none was sent. |
| 400 | custom_amount_not_allowed | The link has a fixed amount; don't send one. |
| 400 | duplicate_transaction_hash | That transaction hash was already used for another payment. |
| 400 | transaction_not_refundable | The payment isn't in a refundable state. |
| 400 | refund_amount_exceeds_remaining | More than the remaining refundable balance. |
| 400 | canary_merchant_not_allowed | Merchant isn't on the live mainnet allowlist yet. |
| 400 | canary_payment_cap_exceeded | The payment is above the beta per-payment cap. |
| 503 | wallet_payments_not_configured | The server has no blockchain client configured. |
| 503 | price_unavailable | No exchange rate could be obtained to quote the payment. Safe to retry in a moment. |
Rate limits
| Surface | Limit |
|---|---|
| API-key endpoints (payments, refunds, events, disputes) | 100 requests per minute |
| Public checkout endpoints | 30 requests per minute |
| Dashboard sign-in | 5 attempts per minute |
Going over a limit returns 429. Note that this response uses a flatter body than the standard envelope:
- Back off and retry after a short delay rather than retrying immediately.
- Prefer webhooks over polling to stay well under the limit.
Idempotency
Operations that move money require an Idempotency-Key header. Creating a refund is the one you'll use from your server. Retrying with the same key returns the original result instead of repeating the action. Use a fresh random key (a UUID) per intended action, and reuse it only when retrying that same action.