Handling Errors
Every error response uses the same envelope:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Amount must be greater than zero",
"details": {
"amount": "must be a positive integer"
}
}
}
details is optional and, when present, is usually a map of field name to what's wrong with it.
Common codes
| Code | Typical status | Meaning |
|---|---|---|
BAD_REQUEST | 400 | The request was malformed |
VALIDATION_ERROR | 400 | A field failed validation — check details |
UNAUTHORIZED | 401 | Missing or invalid token |
FORBIDDEN | 403 | Valid token, but it lacks the required scope, or a business/verification gate wasn't met |
NOT_FOUND | 404 | The resource doesn't exist, or doesn't belong to your application |
CONFLICT | 409 | e.g. a payment intent that's already in a terminal state |
IDEMPOTENT_REQUEST_IN_FLIGHT | 409 | See Idempotent Requests |
INTERNAL_SERVER_ERROR | 500 | Something went wrong on our end — safe to retry |
This list isn't exhaustive — treat error.code as a machine-readable hint for common cases, and always fall back to the HTTP status code and error.message for anything not listed here.
One important exception
GET /oauth/authorize and POST /oauth/authorize/decision are browser-facing, not API calls your integration makes directly. Their errors are either an HTML page (if something's wrong before a redirect URI can be trusted, like an unknown client) or a redirect back to your redirect_uri with RFC 6749-style error and error_description query parameters — not this JSON envelope.