# Error Handling

Check HTTP status first, then read the validation fields. Invalid syntax, missing mail DNS, and disposable detection return `200`; this means the check completed, not that the address passed.

Requests the API cannot process return a non-success status, usually with a JSON `error` message. Do not require that body shape to recognize a failure: session errors and upstream failures may return a different JSON structure or HTML.

## Validation results are not errors

| Input | Response | Status |
| --- | --- | --- |
| Malformed email (`not-an-email`) | `{ "format": false, ... }` | `200` |
| Missing/empty `email` or `domain` | `{ "format": false, ... }` | `200` |
| Domain with no mail servers | `{ "dns": false, ... }` | `200` |
| Temporarily inconclusive DNS result | Result + `dns_indeterminate` signal | `200` |
| Disposable address | `{ "disposable": true, ... }` | `200` |
| Address rejected by the paid relay setting | `{ "blocked": true, ... }` | `200` |
| Inconclusive relay rejection decision | `{ "blocked": null, ... }` | `200` |
| Check requested through `features` but not on your plan | Result + `requires_upgrade` array | `200` |

An explicit `block_privacy_relays` option requires paid access, even when set to `false`. Without access it returns `403`; an invalid value returns `422`. See [Privacy Forwarding](https://docs.disify.com/api/forwarding.md#request-override).

## Error response

A typical API error:

```json
{
  "error": "Invalid API key"
}
```

Rate-limit and quota errors include extra context to help you back off or upgrade:

```json
{
  "error": "Rate limit exceeded",
  "retry_after": 60,
  "upgrade_url": "https://disify.com/pricing",
  "upgrade_message": "Need higher limits? Create a free account for 60 req/min, or upgrade to Pro for 500 req/min."
}
```

## Status codes

| Code | Meaning | Example messages |
| --- | --- | --- |
| `200` | Check completed; read the validation fields to decide whether to accept the input. | — |
| `401` | Invalid API key. | `Invalid API key` |
| `403` | Access denied, account suspended, or explicit paid policy unavailable. | `Access denied`, `Account suspended`, `Privacy relay policy requires a paid plan. No validation was performed.` |
| `404` | Bulk session is invalid, missing, or expired. | Body may be HTML. |
| `413` | Bulk input exceeds 2,000,000 bytes. | `Bulk input is too large.` |
| `422` | Unprocessable input or invalid request option. | `Input too long`, `Maximum 10,000 unique emails allowed.`, `Invalid validation setting. No validation was performed.` |
| `429` | Rate limit or quota exceeded, or too many invalid key attempts. | `Rate limit exceeded`, `New free account daily quota exceeded`, `Free daily validation quota exceeded`, `Free monthly quota exceeded`, `Too many invalid API key attempts` |
| `503` | A required service or bounded processing buffer is temporarily unavailable. | `Service temporarily unavailable` |

## Handling rate limits

- **Per-minute limits:** wait for `Retry-After` / `retry_after` before retrying.
- **Daily or monthly quotas:** use `reset_at`; rapid retries will not help.
- **Bulk admission limits:** wait for the current job or the cooldown described in the message.
- **Invalid-key attempt limits:** fix authentication before retrying.

Retry and upgrade fields are not present on every `429`. See [Rate Limits](https://docs.disify.com/guide/rate-limits.md) and [Bulk Validation](https://docs.disify.com/api/bulk.md#errors).

## Handling temporary unavailability

A `503` is retryable. The generic `Service temporarily unavailable` response includes both a `retry_after` value and a `Retry-After` header; wait at least that many seconds and retry with jitter. Other `503` responses may provide retry guidance only in the message. Never treat a `503` as an invalid API key or prompt the customer to replace their key.

A core result containing the `dns_indeterminate` signal is also retryable, but remains a `200` because non-DNS checks completed normally. Do not interpret its `dns: false` value as confirmed absence of mail servers. Requested fields listed in `temporarily_unavailable_features` should likewise be retried later.

Source: https://docs.disify.com/api/errors.html
