# Errors

> Every error returns a JSON body with statusCode, name, and message.

SendPing uses conventional HTTP status codes: `2xx` for success, `4xx` for problems with the request (a missing field, an invalid key, an unverified domain), and `5xx` for server errors. Every error response has the same shape:

```json
{
  "statusCode": 403,
  "name": "invalid_api_key",
  "message": "API key is invalid or has been revoked."
}
```

## Error names

| name | Status | Meaning |
| --- | --- | --- |
| `invalid_idempotency_key` | 400 | The `Idempotency-Key` is malformed. It must be between 1 and 255 characters. |
| `missing_api_key` | 401 | No API key in the authorization header. Include `Authorization: Bearer YOUR_API_KEY`. |
| `restricted_api_key` | 401 | This API key is restricted to only send emails. Use a key with full access for other actions. |
| `plan_limit_reached` | 402 | A plan cap blocks the request — most often more verified domains than the plan allows. Remove the excess or upgrade. |
| `invalid_api_key` | 403 | The API key is invalid, expired, or revoked. Generate a new key in the dashboard. |
| `dashboard_only` | 403 | The endpoint refuses API-key callers outright, whatever their permission — unlike `restricted_api_key`, no key can satisfy it. Creating, editing and revoking API keys is dashboard-only: do it under **API Keys** in the dashboard. |
| `view_only_member` | 403 | You are signed in as a view-only member of someone else’s team. Reads succeed; creating, changing, deleting and sending are refused until the owner gives you the admin role. (API-key callers get `restricted_api_key` instead.) |
| `not_found` | 404 | The requested endpoint or resource does not exist (or is not yours). |
| `invalid_idempotent_request` | 409 | The same idempotency key was used with a different request payload. Change the key or the payload. |
| `concurrent_idempotent_requests` | 409 | The same idempotency key was used while the original request is still in progress. Try again later. |
| `domain_conflict` | 409 | The domain — or a parent/subdomain of it — is already verified by another account. Use `POST /domains/claim` to prove ownership. |
| `invalid_attachment` | 422 | An attachment must have either `content` or `path`. |
| `validation_error` | 422 | One or more fields failed validation, or the request is rejected for a domain-related reason (e.g. the `from` domain is not verified). The message details which. |
| `invalid_from_address` | 422 | The `from` field is invalid. Use `email@example.com` or `Name <email@example.com>`. |
| `invalid_to_address` | 422 | A recipient address is not a valid email address. |
| `missing_required_field` | 422 | The request body is missing one or more required fields (e.g. `from`, `to`, `subject`). |
| `reserved_recipient` | 422 | A recipient is on a domain reserved for documentation or local testing — `example.com`, `example.net`, `example.org`, or any address under `.test`, `.invalid`, `.localhost` or `.example`. The email was rejected before the provider handoff and nothing was sent or charged; send to a real mailbox. This is a fail-closed backstop: on [POST /emails](https://www.sendping.co/docs/api/emails-send) and [POST /emails/batch](https://www.sendping.co/docs/api/emails-batch) such recipients are dropped earlier as suppressed, so what you actually see is `validation_error` (422) with the message *All `to` recipients are suppressed*. The [mailbox simulator](https://www.sendping.co/docs/emails/send-test) addresses at `test.sendping.co` are the exception — they are simulated, not rejected. |
| `daily_quota_exceeded` | 429 | You have reached your daily email quota. Wait 24 hours or upgrade your plan. Sent and received emails both count. |
| `monthly_quota_exceeded` | 429 | You have reached your monthly email quota. Upgrade your plan to increase it. Sent and received emails both count. |
| `contact_limit_reached` | 429 | Legacy compatibility code; current plans provide unlimited stored contacts. Request-size and rate limits still apply. |
| `ai_credits_exceeded` | 429 | You have used every AI credit in the rolling 30-day window. Upgrade your plan for more. |
| `automation_quota_exceeded` | 429 | You have used every automation run in the rolling 30-day window. Upgrade your plan to run more. |
| `rate_limit_exceeded` | 429 | Too many requests. Read the rate-limit response headers and reduce your request rate. |
| `contacts_busy` | 503 | Another contact write for the same account — usually a bulk import — was still holding the account’s contact-limit lock, so this request was refused before it changed anything. Honor `retry-after` and retry; the same request then succeeds. Prefer one [POST /contacts/batch](https://www.sendping.co/docs/api/contacts-batch) over many concurrent single creates, which is what makes this contention in the first place. |
| `contacts_timeout` | 503 | A contact write took longer than its time bound and was rolled back, so nothing changed. Honor `retry-after` and retry; if it recurs, import in smaller batches. |
| `batch_incomplete` | 503 | A synchronous [batch send](https://www.sendping.co/docs/api/emails-batch-delivery) ran out of request time before finishing. The body lists exactly which emails were sent (`sent`, `sent_count`) — the remainder were not. `503` with `retry-after` applies when the request carried an `Idempotency-Key`, because retrying that key replays this same answer rather than re-sending. **Without a key the same error is returned as `422` instead**, deliberately: `422` is not automatically retried, and a retry would re-send the emails that were already delivered. |
| `reputation_limit_exceeded` | 429 | The account or domain reached its current reputation warm-up/recovery capacity. Honor `Retry-After`; no email was sent. |
| `reputation_paused` | 403 | Sending is paused for the account or domain. Do not retry automatically; contact support to request a reputation review. |
| `sending_service_unavailable` | 503 | A platform-wide reputation safety check temporarily paused sending. Honor `Retry-After`; no email was sent. |
| `sending_configuration_unavailable` | 503 | A SendPing-side sending configuration was momentarily out of step, so the provider refused the send before delivery. Nothing was sent and nothing was charged, and no change to your request can fix it: retry the request (honoring `retry-after` when present), and contact support@sendping.co if it persists. |
| `application_error` | 500 | An unexpected error occurred. Safe to retry idempotent requests later. |
| `internal_server_error` | 500 | An unexpected error occurred. Try the request again later. |

> **Note:** The `validation_error` name normally carries a `422` status and covers both field-level and domain-related rejections, so read `message` to tell them apart. The one exception is the missing-`User-Agent` rejection, which returns `validation_error` with a `403`. A domain that is already verified by a **different** account is its own name — `domain_conflict` (409).
