Errors
Every error, on every endpoint, uses the same JSON envelope:
Error response
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"issues": [ /* present only for VALIDATION_ERROR */ ]
}
}Error codes
| Status | Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST | The request was malformed in a way not covered by validation - e.g. an empty resolved message body. |
| 400 | VALIDATION_ERROR | The request body failed schema validation. An `issues` array with field-level detail is included. |
| 401 | UNAUTHORIZED | Missing, invalid, revoked, or expired credentials - API key or JWT. |
| 402 | PAYMENT_REQUIRED | The account's SMS balance is insufficient to send. |
| 403 | FORBIDDEN | Authenticated, but missing the required permission or API key scope. |
| 404 | NOT_FOUND | The resource doesn't exist, or doesn't belong to your account. |
| 409 | CONFLICT | The request conflicts with existing data - e.g. an email already registered. |
| 429 | TOO_MANY_REQUESTS | Rate limit exceeded. See Rate limits for the specific limits. |
| 503 | SERVICE_UNAVAILABLE | A required upstream integration isn't configured or is temporarily unavailable. |
| 500 | INTERNAL_ERROR | An unexpected server error. Safe to retry; contact support if it persists. |
A note on 401 responses
Login and social-auth failures always return the same generic message - "Invalid email or password" - whether the email is unknown, the password is wrong, or the account only has social login configured. This is deliberate: it avoids letting an attacker use the error message to enumerate which emails have accounts.