Errors
One error shape, everywhere.
Every error returns the same envelope, so a client parses one shape:
{
"name": "UnauthorizedError",
"code": "missing_api_key",
"message": "Missing API key.",
"fix": "Send `Authorization: Bearer <key>`.",
"docs": "https://docs.carlyemail.com/authentication"
}
name stringrequired
The error class. Stable — safe to branch on.
message stringrequired
Human-readable. Not stable; do not parse it.
code string
Machine-readable reason. This is what to branch on.
fix string
What to actually do about it, when there is a specific answer.
docs string
A link to the page that explains it.
Status codes
| Status | Meaning |
|---|---|
400 |
The request is malformed or a field is invalid |
401 |
No key, or the key is invalid or revoked |
403 |
Authenticated, but not allowed — wrong scope, missing permission, unverified organization, or suspended |
404 |
No such resource, or not one this key can see |
409 |
Conflict — a username already taken, for instance |
429 |
Quota exceeded. Carries Retry-After |
Codes worth handling
| Code | Status | What it means |
|---|---|---|
missing_api_key |
401 | No Authorization header |
invalid_api_key |
401 | Unknown or revoked key |
invalid_token |
401 | An OAuth access token failed validation |
organization_unverified |
403 | Confirm the owner email before sending |
organization_suspended |
403 | Contact support |
insufficient_permissions |
403 | The key's permission map does not grant this |
inbox_out_of_scope |
403 | An inbox-scoped key reached for a different inbox |
inbox_limit_reached |
429 | Plan inbox cap |
domain_limit_reached |
429 | Plan custom-domain cap |
pod_limit_reached |
429 | Plan pod cap |
webhook_limit_reached |
429 | Plan webhook cap |
daily_limit_reached |
429 | Free-tier daily send cap — carries Retry-After |
monthly_limit_reached |
429 | Plan monthly send cap — carries Retry-After |
storage_limit_reached |
429 | Plan storage cap. Sending stops; receiving does not |
username_unavailable |
409 | Usernames are globally unique per domain |
recipient_blocked |
403 | The address is on a send or reply block list — see lists |
recipient_not_allowed |
403 | A send allow list exists and the address is not on it |
A note on 403 versus 404
A resource belonging to another organization returns 404, not 403. Confirming
that something exists but is not yours is itself a disclosure, so the answer is
the same as if it did not exist.