> ## Documentation Index
> Fetch the complete documentation index at: https://docs.signuprisk.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Reference

> Understand Signup Risk API error responses, status codes, retry behavior, and remediation.

Every failed request returns a JSON error body designed to answer three questions: what failed, whether you can retry, and what to change.

## Error envelope

Errors are wrapped in a single `error` object:

```json theme={null}
{
  "error": {
    "code": "INVALID_API_KEY",
    "message": "The provided API key is invalid.",
    "type": "authentication_error",
    "request_id": "req_c7f3a19d4e6b8205f1a9c3d7e5b41068",
    "retryable": false
  }
}
```

| Field                                                   | Present                     | Meaning                                                                                                                                                                                   |
| ------------------------------------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `code`                                                  | always                      | Machine-readable error code — the stable contract; never parse `message`                                                                                                                  |
| `message`                                               | always                      | Human-readable description of the failure                                                                                                                                                 |
| `type`                                                  | always                      | Category: `authentication_error`, `authorization_error`, `validation_error`, `quota_error`, `rate_limit_error`, `conflict_error`, `not_found_error`, `service_error`, or `internal_error` |
| `request_id`                                            | on analysis endpoints       | Correlation ID matching the `X-Request-ID` response header                                                                                                                                |
| `retryable`                                             | on analysis endpoints       | Whether retrying the same request can succeed                                                                                                                                             |
| `retry_after_seconds`                                   | quota and rate-limit errors | Seconds to wait; always identical to the `Retry-After` header                                                                                                                             |
| `details`                                               | code-specific               | Safe, typed context — for example `required_scope`, the rate-limit window, or the quota snapshot                                                                                          |
| `action`                                                | remediable errors           | First-party remediation link (upgrade, create a key, contact sales)                                                                                                                       |
| `upgrade_required`, `required_plan`, `recommended_plan` | plan-gated errors           | Legacy upgrade context; prefer `action`                                                                                                                                                   |

All responses — success or error — carry an `X-Request-ID` header. Keep it when contacting support; it identifies the exact request.

## HTTP status categories

| Status | Meaning                                                                                          |
| ------ | ------------------------------------------------------------------------------------------------ |
| 400    | The request is invalid — malformed JSON, a missing or invalid field, or an unknown `options` key |
| 401    | Authentication failed — the API key is missing, malformed, invalid, revoked, or expired          |
| 403    | Authenticated but not allowed — missing scope, plan gate, or a disabled account                  |
| 404    | The referenced object does not exist (or does not belong to your account)                        |
| 405    | The HTTP method does not match the endpoint                                                      |
| 409    | The operation conflicts with current state                                                       |
| 410    | The resource is gone — bulk results past their expiry                                            |
| 413    | The request body or upload is too large                                                          |
| 415    | The upload's content type is wrong                                                               |
| 429    | Rate limit, quota exhaustion, or a duplicate in-flight request                                   |
| 500    | Unexpected server-side failure                                                                   |

## Error catalog

The complete set of public error codes across the customer-facing API:

| Status | Code                     | Meaning                                                                                                    | Retry?       | What to do                                                                                            |
| ------ | ------------------------ | ---------------------------------------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------- |
| 400    | `INVALID_REQUEST`        | Malformed JSON, empty body, invalid field, unknown `options` key, or an invalid bulk CSV / webhook payload | No           | Fix the request; see [validation errors](#validation-errors-400) below                                |
| 400    | `INVALID_EMAIL`          | The `email` field is missing                                                                               | No           | Include `email`                                                                                       |
| 400    | `MALFORMED_EMAIL`        | The address is not a valid email (for example, longer than 254 characters)                                 | No           | Correct the address                                                                                   |
| 400    | `LIMIT_EXCEEDED`         | Batch larger than 50 addresses                                                                             | No           | Split into batches of 50 or fewer                                                                     |
| 401    | `MISSING_API_KEY`        | No `Authorization` header                                                                                  | No           | Send `Authorization: Bearer <key>`                                                                    |
| 401    | `MALFORMED_API_KEY`      | The `Authorization` header is not a well-formed Bearer token                                               | No           | Use the documented header form                                                                        |
| 401    | `INVALID_API_KEY`        | The key is unknown or incorrect, including keys from the wrong environment                                 | No           | Check the key and its environment                                                                     |
| 401    | `REVOKED_API_KEY`        | The key has been revoked                                                                                   | No           | Create and use a new key                                                                              |
| 401    | `EXPIRED_API_KEY`        | The key's expiration has passed                                                                            | No           | Create a new key or rotate the expired one                                                            |
| 403    | `INSUFFICIENT_SCOPE`     | The key lacks the scope required by the endpoint (`details.required_scope` names it)                       | No           | Add the scope to the key in the dashboard                                                             |
| 403    | `FEATURE_NOT_AVAILABLE`  | The feature requires a higher plan (bulk, webhooks)                                                        | No           | Upgrade the plan                                                                                      |
| 403¹   | `ACCOUNT_DISABLED`       | The account owning the key is disabled                                                                     | No           | Contact support                                                                                       |
| 404    | `BULK_JOB_NOT_FOUND`     | No bulk job with this ID exists for your account                                                           | No           | Check the job ID                                                                                      |
| 404    | `NOT_FOUND`              | No webhook config with this ID exists for your account, or a result artifact is unavailable                | No           | Check the resource ID                                                                                 |
| 405    | `INVALID_REQUEST`        | Wrong HTTP method for the endpoint                                                                         | No           | Use the documented method                                                                             |
| 409    | `CONFLICT`               | An active bulk job already exists for the account                                                          | No           | Wait for it to finish; resubmitting the identical file reuses the existing job instead of conflicting |
| 409    | `RESULT_NOT_READY`       | The bulk job has not completed yet                                                                         | Poll         | Poll the job status until it completes                                                                |
| 410    | `GONE`                   | The bulk job's results have expired                                                                        | No           | Submit a new job                                                                                      |
| 413    | `INVALID_REQUEST`        | Request body over 1 MB (JSON endpoints) or upload over the bulk file limit                                 | No           | Reduce the payload size                                                                               |
| 415    | `INVALID_REQUEST`        | Bulk upload is not `multipart/form-data` with a `file` field                                               | No           | Send the CSV as a multipart file upload                                                               |
| 429    | `RATE_LIMIT_EXCEEDED`    | Per-minute plan limit or anti-abuse limit hit                                                              | Yes          | Honor `Retry-After` / `retry_after_seconds`; reduce concurrency                                       |
| 429    | `MONTHLY_QUOTA_EXCEEDED` | Monthly analysis allowance exhausted (grace included)                                                      | After reset  | Upgrade the plan or wait for `reset_at`                                                               |
| 429    | `DUPLICATE_REQUEST`      | An identical request is still in flight                                                                    | No           | Wait for the original request to finish                                                               |
| 500    | `INTERNAL_ERROR`         | Unexpected server-side failure                                                                             | Conservative | Retry with backoff; keep the `X-Request-ID` for support                                               |

¹ `ACCOUNT_DISABLED` returns `403` on `POST /v1/analyze-email` and `401` on the batch, bulk, and webhook endpoints.

Not-found lookups are scoped to your account: a resource belonging to another account is indistinguishable from one that does not exist.

## Validation errors (400)

Common triggers, all returning `INVALID_REQUEST` unless noted:

* malformed or empty JSON body
* unknown keys inside `options` — only `include_checks` and `include_explanation` are accepted
* a `name` longer than 200 characters
* a batch with a missing, empty, or oversized `emails` array
* bulk CSV problems: missing `email` column, empty or malformed file, or exceeding the row limit
* webhook configuration problems: non-HTTPS URLs, unknown event types, or an out-of-range `high_risk_threshold`
* an invalid `status` filter on the webhook deliveries endpoint

## Authentication and authorization errors

401 and 403 answer different questions. A **401** means Signup Risk API could not establish who you are: the key is missing (`MISSING_API_KEY`, `MALFORMED_API_KEY`), unknown or wrong-environment (`INVALID_API_KEY`), revoked (`REVOKED_API_KEY`), or expired (`EXPIRED_API_KEY`). A **403** means the identity is valid but not permitted: the key lacks the endpoint's scope (`INSUFFICIENT_SCOPE`, with `details.required_scope` naming the missing scope), the account's plan does not include the feature (`FEATURE_NOT_AVAILABLE`, with upgrade metadata), or the account is disabled (`ACCOUNT_DISABLED`).

See [Authentication](/authentication) for the key model and [Warnings & Quotas](/warnings-and-quotas) for plan gates.

## 429: rate limit, quota, or duplicate

Three conditions share status 429 and are distinguished by `code`:

| Code                     | Condition                                                                            | `Retry-After`                                      | Remedy                            |
| ------------------------ | ------------------------------------------------------------------------------------ | -------------------------------------------------- | --------------------------------- |
| `RATE_LIMIT_EXCEEDED`    | Per-minute plan limit (counted in analyzed units) or an unpublished anti-abuse limit | Present for the plan limit; absent for anti-abuse  | Slow down; honor the retry window |
| `MONTHLY_QUOTA_EXCEEDED` | Monthly allowance exhausted                                                          | Present — seconds to the reset, capped at 24 hours | Upgrade or wait for `reset_at`    |
| `DUPLICATE_REQUEST`      | Identical request still in flight                                                    | Absent                                             | Wait for the original request     |

Quota errors carry a `details` snapshot (`plan`, `cap`, `grace`, `hard_stop`, `current_usage`, `requested_units`, `reset_date`) and an upgrade `action`. See [Warnings & Quotas](/warnings-and-quotas) for the full quota model.

## Server errors (5xx)

Unexpected failures return `500` `INTERNAL_ERROR` with a fixed, safe message — never a stack trace or internal detail. Treat client errors (4xx) as deterministic: retrying an unchanged request will fail again. For a 500, a conservative retry with exponential backoff is reasonable; if the failure persists, contact support with the `X-Request-ID`.

## Next steps

<Card title="API reference" icon="square-code" href="/api-reference">
  Exact request and response schemas for every customer-facing operation.
</Card>
