Errors
Error response format and status codes.
All error responses follow a consistent format.
Error Response Format
{
"error": {
"code": "USER_NOT_FOUND",
"message": "User not found",
"detail": null
}
}
| Field | Type | Description |
|---|---|---|
code | string | Machine-readable error code for programmatic handling |
message | string | Human-readable error description |
detail | object | null | Additional context when available |
HTTP Status Codes
| Status | Meaning |
|---|---|
400 | Bad Request. The request is malformed or the endpoint rejected it. |
401 | Unauthorized. Invalid or missing API key. |
403 | Forbidden. Valid API key but insufficient permissions, or the client IP is outside the key's allowlist (API_KEY_IP_NOT_ALLOWED). |
404 | Not Found. Resource does not exist. |
409 | Conflict. Resource already exists or state conflict. |
422 | Unprocessable Entity. The request failed validation. See Validation errors. |
429 | Too Many Requests. Rate limit exceeded. See Rate Limiting. |
500 | Internal Server Error. Contact support if persistent. |
Errors any endpoint can return
Each endpoint page in the API reference lists the errors specific to that endpoint. The errors below can come from any endpoint, so they are documented only here.
| Status | Code | Cause |
|---|---|---|
400 | API_VERSION_HEADER_MISSING | The Leeward-Version header is missing. See Versioning. |
400 | API_VERSION_INVALID | The Leeward-Version header is not a supported version. |
400 | PARSE_ERROR | The request body could not be parsed. |
400 | IDEMPOTENT_PARAMETER_MISMATCH | A POST reused an Idempotency-Key with a different body. See Idempotency. |
401 | API_KEY_REQUIRED | The Authorization header is missing. See Authentication. |
401 | INVALID_AUTH_HEADER | The Authorization header is not a Bearer token. |
401 | INVALID_API_KEY | The API key is unknown or revoked, or it belongs to another environment or region. |
403 | API_KEY_IP_NOT_ALLOWED | The request came from an IP address outside the key's allowlist. |
403 | MODULE_NOT_ENABLED | The project does not have the module the endpoint belongs to. detail.module names it. |
403 | FEATURE_NOT_ENABLED | The project does not have the feature the endpoint needs. detail.feature names it. |
404 | ROUTE_NOT_FOUND | No endpoint matches the method and path. |
409 | IDEMPOTENCY_REQUEST_IN_PROGRESS | Another POST with the same Idempotency-Key is still running. |
422 | VALIDATION_FAILED | The request does not match the endpoint's schema. See Validation errors. |
429 | RATE_LIMIT_EXCEEDED | The project exceeded its rate limit. See Rate Limiting. |
500 | INTERNAL_SERVER_ERROR | The request failed on our side. |
Validation errors
A request that does not match the endpoint's schema returns 422 with code VALIDATION_FAILED. The detail object says what failed:
onnames the part of the request that failed:body,query,paramsorheaders.errorslists each problem. Every entry has apathto the field and amessage. Most entries also carry acodefor the rule that failed.
For example, sending cardType instead of type to POST /cards returns:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Validation failed",
"detail": {
"on": "body",
"errors": [
{
"path": "type",
"message": "Invalid option: expected one of \"VIRTUAL\"|\"PHYSICAL\"",
"code": "invalid_value"
}
]
}
}
}
The API ignores fields it does not recognize. A misspelled field therefore shows up as an error on the required field it was meant to fill. Nested fields use dot-separated paths, such as config.maxAmount.
A few checks run after schema validation, such as decoding a pagination cursor. These also return VALIDATION_FAILED, with a shorter detail in place of errors.
Handling Errors
Use the code field for programmatic error handling. Branch on code rather than the HTTP status, because several errors can share a status. For example, a few endpoints use 422 for their own business-rule errors alongside VALIDATION_FAILED.
if (response.error.code === 'USER_NOT_FOUND') {
// Handle missing user
} else if (response.error.code === 'PHONE_NUMBER_ALREADY_EXISTS') {
// Handle duplicate phone
}
Retries
For 5xx errors, retry with exponential backoff. For 429, wait for the Retry-After header. For other 4xx errors, fix the request before retrying.
