All errors share one shape — status is always false, error_code is
stable and machine-readable:
"error_code": "UNAUTHORIZED",
"description": "Human-readable message"
Match on error_code, never on description (wording may change).
| HTTP | Code | When |
|---|
| 401 | UNAUTHORIZED | Missing or unrecognized credentials |
| 401 | INVALID_TOKEN | JWT invalid, malformed or revoked |
| 401 | TOKEN_EXPIRED | JWT past expiry |
| 401 | INVALID_API_KEY | API key not found or hash mismatch |
| 401 | API_KEY_INACTIVE | API key disabled |
| 401 | API_KEY_EXPIRED | API key expired |
| 401 | INVALID_CREDENTIALS | Wrong username/password |
| 401 | USER_NOT_ACTIVE | Account suspended or inactive |
| 401 | IP_NOT_ALLOWED | Client IP not in the allowed list |
| 401 | SIGNATURE_REQUIRED | Key requires HMAC but signature headers missing |
| 401 | INVALID_SIGNATURE | HMAC signature failed verification |
| 403 | FORBIDDEN | Authenticated, but route or permission denied |
| 403 | NO_SMS_ACCESS | SMS not configured on the account |
| 403 | CONFIG_ERROR | Credentials incomplete — contact support |
| 403 | CROSS_TENANT_WORKSPACE | X-Workspace-ID names a workspace you don’t own (or deleted) |
| 403 | NO_MAIN_WORKSPACE | No live main workspace — contact support |
| 403 | WORKSPACE_NOT_AVAILABLE | Target workspace deleted — use a live workspace |
| HTTP | Code | When |
|---|
| 400 | INVALID_BODY | Malformed JSON body |
| 400 | INVALID_PARAMS | Invalid query or path parameters |
| 400 | INVALID_TO | Missing/empty recipients |
| 400 | INVALID_MESSAGE | Missing/empty message |
| 400 | INVALID_SENDER | Missing/empty sender_id |
| 400 | TOO_MANY_RECIPIENTS | Over 100 recipients |
| 400 | INVALID_WORKSPACE_HEADER | X-Workspace-ID is not a positive integer |
| 402 | INSUFFICIENT_BALANCE | Balance too low — body includes cost breakdown |
| HTTP | Code | When |
|---|
| 429 | RATE_LIMIT_EXCEEDED | Per-account rate limit hit |
| 429 | TOO_MANY_ATTEMPTS | Login/2FA brute-force limit (includes Retry-After) |
Details and recommended client behavior: rate limits.
| HTTP | Code | When |
|---|
| 404 | NOT_FOUND | Message or resource not found |
| 500 | INTERNAL_ERROR | Server-side error |
| 500 | DB_ERROR | Send request could not be queued — atomic rollback, safe to retry |
- Retry
429 (after a delay) and 500 DB_ERROR (idempotent by design).
Do not blind-retry 4xx — fix the request instead.
- Alert on
402 INSUFFICIENT_BALANCE and credit_blocked from
balance before campaigns stall.
- Re-authenticate on
401 TOKEN_EXPIRED / INVALID_TOKEN if using JWT.
- Log
error_code + description + the request ID from your HTTP client
for support escalations.