Errors
The error envelope and every error name the Bytesms API returns.
Error shape
Every /api/v1 error is JSON with the HTTP status, a human-readable message and a machine-readable name. Branch on name (and the status), never on message.
{
"statusCode": 422,
"message": "`to` must contain at least one recipient.",
"name": "validation_error"
}Some errors add fields. Quota errors keep the internal code plus limit and current; plan-limit errors carry the specific code:
{
"statusCode": 429,
"message": "Daily email limit reached (100/100). Try again tomorrow or upgrade your plan.",
"name": "daily_quota_exceeded",
"code": "daily_limit_reached",
"limit": 100,
"current": 100
}Request-body validation failures (wrong types, invalid email addresses, unknown enum values) are returned as 422 validation_error with the individual problems joined into message.
Error names
| Status | name | When |
|---|---|---|
| 401 | unauthorized | Missing, malformed, unknown or expired API key. |
| 401 | restricted_api_key | A sending_access key called an endpoint other than sending. |
| 403 | restricted_api_key | A domain-scoped key sent from another domain (or its domain was deleted). |
| 403 | suspended_api_key | The API key was suspended. |
| 403 | invalid_from_address | The from domain is not a verified domain of this workspace (the workspace has other verified domains). |
| 403 | validation_error | No verified domain yet, or [email protected]used to send to someone outside the workspace. Also returned when a domain's warm-up daily volume is used up. |
| 403 | sending_paused | Sending is paused for the workspace (e.g. high bounce/complaint rate). |
| 403 | account_under_review | The workspace is under review. |
| 403 | account_suspended | The workspace is suspended. |
| 403 | subscription_past_due_expired, subscription_unpaid, subscription_incomplete, subscription_paused, subscription_canceled | The paid subscription is not in good standing (past the payment grace period, unpaid, …). |
| 403 | plan_limit_exceeded | A plan cap was hit; code says which: api_key_limit_reached, domain_limit_reached, segment_limit_reached, contact_limit_reached, contact_limit_exceeded, webhook_limit_reached, member_limit_reached. |
| 404 | not_found | The resource does not exist in this workspace (ids of other workspaces are 404 too). |
| 409 | idempotency_key_conflict | The Idempotency-Key was already used with a different request body. |
| 409 | domain_in_use, domain_pending_elsewhere | The domain is verified in another workspace, or claimed there within the last 72 hours. |
| 409 | validation_error | Duplicate: a contact with this email already exists in the audience, a template alias is taken, a broadcast was already sent. |
| 422 | validation_error | The request is invalid (see message). |
| 422 | invalid_idempotency_key | The Idempotency-Key header is empty or longer than 256 characters. |
| 422 | attachments_not_supported | The request contained attachments (not supported yet). |
| 422 | invalid_domain, reserved_domain | Not a valid fully-qualified domain name, or a platform / consumer-mailbox domain that can't be added. |
| 429 | rate_limit_exceeded | Too many requests per second — see Rate limits. |
| 429 | daily_quota_exceeded | The plan's daily email limit (recipients per UTC day) is used up. |
| 429 | monthly_quota_exceeded | The plan's monthly quota is used up and pay-as-you-go can't cover more. |
| 429 | overage_cap_reached | Pay-as-you-go overage reached its safety cap for the billing period. |
| 429 | spend_limit_reached | The pay-as-you-go spend limit you set for the workspace was reached. |
| 5xx | application_error | Something went wrong on our side, or a sending server was unreachable. Retry with backoff. |
Retrying
Retry 429 rate_limit_exceeded after the retry-after header and 5xx errors with exponential backoff. When you retry a send, use an Idempotency-Keyso a request that actually succeeded is not sent twice. Don't retry other 4xx errors unchanged — they fail the same way.
Legacy endpoint
POST /api/v1/emails/send is not wrapped in this envelope: request-validation failures come back as 400 with { statusCode, message: string[], error }, and it returns the full internal message row on success. New integrations should use POST /api/v1/emails.