# Errors

URL: https://email-validation.org/docs/errors/
Updated: 2026-10-20

> Documentation index: https://email-validation.org/llms.txt

When a request fails, the API answers with an HTTP status and a JSON body that names the problem. **No credit is
ever used for a request that ends in an error.**

## The error body

```json
{
  "error": {
    "code": "insufficient_credits",
    "message": "Not enough credits. Buy a pack to continue.",
    "request_id": "req_7Hq2LmX9cV4bN8tR1zK6wD"
  }
}
```

- **`code`** is stable and machine-readable: branch on it.
- **`message`** is for people and may change: do not parse it.
- **`request_id`** identifies the request (also in the `X-Request-Id` header). Quote it when you contact support.

`429` and `503` answers also carry `Retry-After`: the number of seconds to wait before trying again.

## Every error code

| Code | HTTP | When it happens | What to do | Retry? |
|---|---|---|---|---|
| `invalid_request` | 400 | A parameter is missing, has the wrong type or is out of range, or the API key was sent in the URL. The message names the fields at fault. | Correct the request. Send the key in the Authorization header, never in the query string. | No: fix the request |
| `unauthorized` | 401 | No API key was sent, the key is malformed, or it has been revoked or rolled. | Send a current key as `Authorization: Bearer <key>` (or `X-Api-Key`). Create a new key in the dashboard if needed. | No: fix the request |
| `insufficient_credits` | 402 | The account has no credits left for this request (or not enough for a bulk job). | Buy a credit pack in the dashboard, then retry. Nothing was charged for the failed request. | After you fix the account |
| `forbidden` | 403 | The key lacks the scope this endpoint needs (for example `bulk`), or the account is suspended or on a billing hold. | Use a key with the right scope, or check the notices in your dashboard. | After you fix the account |
| `forbidden_origin` | 403 | The key is restricted to a list of browser origins and the request came from another `Origin`. | Call the API from an allowed origin, or update the allowed origins of the key. | No: fix the request |
| `not_found` | 404 | The resource does not exist or belongs to another account. | Check the id. Ids are only visible to the account that created them. | No: fix the request |
| `conflict` | 409 | The request does not fit the current state of the resource, such as starting a bulk job that is already running or downloading results before a job has finished. | Read the job again and act on its current `status`. | No: fix the request |
| `length_required` | 411 | A bulk upload was sent without a `Content-Length` header (for example as a chunked stream). | Send the file with a known length. Most HTTP clients do this when you pass the whole file. | No: fix the request |
| `payload_too_large` | 413 | The request body is over the limit: 100 MB for a bulk upload, 100,000 addresses for a JSON list. | Split the list, or upload large files in the dashboard, which takes files up to 1 GB. | No: fix the request |
| `rate_limited` | 429 | The account or key sent more requests per second than its limit allows. | Wait for the number of seconds in `Retry-After`, then retry. Spread requests evenly over time. | Yes, after `Retry-After` |
| `abuse_throttled` | 429 | Too many recent requests ended without a definite answer (free outcomes) compared with charged ones, so the account is slowed down for a while. | Wait for `Retry-After`. Avoid re-checking the same failing addresses in a tight loop. | Yes, after `Retry-After` |
| `key_daily_cap` | 429 | This key reached the daily credit cap set for it in the dashboard. | Raise or remove the cap for this key in the dashboard, use another key, or wait until the cap resets the next day. | Yes, after `Retry-After` |
| `internal_error` | 500 | Something failed on our side. No credit was used. | Retry later. If it keeps happening, contact support and quote the `request_id`. | Yes, with backoff |
| `service_unavailable` | 503 | Validation is paused for a short time, for example while a dependency recovers. No credit was used. | Wait for `Retry-After`, then retry. In sign-up forms, accept the address and check it later. | Yes, after `Retry-After` |
| `upstream_capacity` | 503 | A bulk job could not be admitted right now because validation capacity is limited. No credit was used. | Start the job again later; it stays in the `ready` state until then. | Yes, after `Retry-After` |

## Retrying safely

- **Retry `429` and `503`** after the number of seconds in `Retry-After`, with a cap on attempts (three is plenty).
  These answers are never charged, so retrying them cannot double-charge.
- **Back off when there is no `Retry-After`:** wait about half a second, then one, then two, adding some randomness so
  many clients do not retry at the same moment.
- **Do not retry `key_daily_cap` or long `abuse_throttled` waits** in a loop: they last until the next day or for
  an hour.
- **Do not retry `4xx` errors** other than `429` without changing the request: the same request will fail again.
- **Network failures:** a check that timed out on your side may have completed on ours. Retrying a validation is
  safe but may cost a second credit; reads (`GET`) can always be retried.

The [official SDKs](https://email-validation.org/docs/sdks/) follow these rules for you.

## Errors in sign-up forms

On a sign-up form, an error from us should never stop a real person. Treat `402 insufficient_credits`,
`429` and `503` (and network failures) **like an `unknown` result**: accept the address, store it, and check it
again later. Only an actual `undeliverable` result should ask the person to correct their address. See the
[sign-up form guide](https://email-validation.org/docs/guides/signup-form-validation/).

## Results that are not errors

A check that could not reach a definite answer is not an error: it is a normal `200` response with the status
`unknown` (free). Handle it as a result; see [the unknown status](https://email-validation.org/docs/statuses/unknown/).
