The Syvel API uses standard HTTP status codes. Every error response includes a JSON body with three fields.

## Response structure

```json
{
  "error": "string",    // short machine-readable code
  "message": "string",  // human-readable explanation
  "detail": "string | null"  // optional additional context
}
```

## HTTP codes summary

| Code | Name | When it occurs |
|------|------|----------------|
| `200` | OK | Request succeeded |
| `401` | Unauthorized | API key missing or invalid |
| `403` | Forbidden | Valid key, but origin not whitelisted |
| `422` | Unprocessable Entity | Email/domain format is invalid |
| `429` | Too Many Requests | Monthly quota exceeded |
| `500` | Internal Server Error | Unexpected server-side error |

---

## 200 — Success

Standard response. See [Domain check](/docs/api/check) for the full response schema.

---

## 401 — Unauthorized

The `Authorization` header is missing, malformed, or contains a key that does not match any active key.

```json
{
  "error": "unauthorized",
  "message": "Missing or invalid API key.",
  "detail": null
}
```

---

## 403 — Forbidden

The API key is valid, but the request `Origin` header does not appear in the key's allowed origins list.

```json
{
  "error": "forbidden",
  "message": "This origin is not authorised for this API key.",
  "detail": "Origin: https://example.com"
}
```

**401 vs 403 — what's the difference?**

| | 401 | 403 |
|---|---|---|
| Key exists? | No (or invalid) | Yes |
| Origin restriction? | N/A | Yes — origin blocked |
| Fix | Check/rotate your key | Add origin in dashboard |

---

## 422 — Unprocessable Entity

The value passed in `{email_or_domain}` does not meet the input validation rules.

```json
{
  "error": "validation_error",
  "message": "Invalid email or domain format.",
  "detail": "Value must be a valid email address or hostname."
}
```

Common causes: empty string, IP address, URL prefix (`http://`), more than one `@`, local part over 64 characters. See [Input validation](/docs/api/check#input-validation) for the full list.

---

## 429 — Too Many Requests

The account's monthly quota has been reached.

```json
{
  "error": "quota_exceeded",
  "message": "Monthly quota reached. Upgrade your plan or wait for the reset.",
  "detail": null,
  "reset_at": "2026-04-01T00:00:00Z"
}
```

The `reset_at` field is an ISO 8601 timestamp indicating when the quota will reset (always the 1st of the next month at 00:00 UTC).

:::caution[Fail open on 429]
Never block a user because the quota is exceeded. Treat a `429` the same as a network error and let the user through.
:::

---

## 500 — Internal Server Error

An unexpected error occurred on Syvel's side. This is rare and transient.

```json
{
  "error": "internal_error",
  "message": "An unexpected error occurred. Please try again.",
  "detail": null
}
```

:::caution[Always fail open on 500]
Do not block users on a `500`. Catch it like any other exception and let the request succeed on your end.
:::