Get started
Errors
Every error returns JSON with a stable code, a human-readable message, and an optionaldetails object for field-specific guidance. HTTP status follows the closest semantic match — 4xx for caller errors, 5xx for ours.
Error body shape
{
"ok": false,
"error": {
"code": "insufficient_scope",
"message": "This endpoint requires scope \"customers:write\".",
"details": {
"requiredScope": "customers:write",
"grantedScopes": ["payments:read", "balance:read"]
}
}
}Every API response — success or failure — is wrapped in an envelope:{ok: true, data: ...} on 2xx, or{ok: false, error: {...}} on 4xx/5xx. Branch on error.code for programmatic handling — messages are subject to copy edits.
Codes
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_failed | Body or query failed Zod schema validation. The response includes a `details` array with field-level issues. |
| 401 | unauthorized | Missing / malformed Authorization header, OR the Bearer token does not match any stored key hash, OR the key has been revoked or has expired. The `message` field distinguishes the cases. |
| 403 | ip_not_allowed | Source IP is not in the key's allowlist. |
| 403 | insufficient_scope | The key was not issued with the scope this endpoint requires. The required scope is echoed in the error body. |
| 403 | mode_mismatch | The endpoint requires a live (or test) key but the caller supplied the other. |
| 403 | merchant_inactive | The merchant account is suspended or closed. |
| 403 | tier_required | Action gated behind a higher KYC tier than the merchant has reached. |
| 402 | plan_required | The merchant's plan does not include this feature. |
| 402 | key_limit_reached | The merchant has hit their plan's live-key cap. Revoke an existing key or upgrade. |
| 404 | not_found | The requested resource doesn't exist or doesn't belong to the calling merchant. |
| 409 | idempotency_key_in_use | The Idempotency-Key was reused with a different request body. Supply a fresh key. |
| 409 | terminal_status | Attempt to transition a resource that's already in a terminal state (e.g. mark-paid a cancelled payout). |
| 422 | stripe_bank_rejected | Underlying Stripe call refused the request. `details.stripeCode` carries the upstream code. |
| 429 | rate_limited | Per-merchant request budget exhausted. Retry after `resetSeconds`. |
| 429 | rate_limited_key | Per-key request budget exhausted (half the merchant cap). |
| 500 | internal_error | Unexpected server-side failure. Try again or contact support if it persists. |
| 502 | screening_unavailable | Compliance screening provider timed out — retryable. |
| 503 | stripe_disabled | The deployment is not configured for card processing. |
Rate limiting
Limits are per merchant plan (10 req/s starter → 250 req/s enterprise), with a per-key sub-budget at half the merchant cap. Both 429 codes include resetSeconds in the error body so callers can back off cleanly.