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

# Error Handling

> Handle Enrow API errors gracefully — response formats, status codes, retry logic, and best practices

The Enrow API signals problems with HTTP status codes and a JSON error body. This guide explains the error response formats, the most common errors and how to fix them, and how to build reliable retry logic. For the complete list of codes, see [Status codes](/status-codes).

## What does an error response look like?

Most Enrow API errors return a simple JSON object with a single `message` field:

```json theme={null}
{
  "message": "Human-readable error description"
}
```

The one exception is an **insufficient-credit (402)** error on **single** endpoints (email/phone find single, verify single), which returns a `reason` field alongside `success`:

```json theme={null}
{
  "reason": "Insufficient credits",
  "success": false
}
```

Error responses never include `error`, `status`, or `retry_after` fields. Read the HTTP status code to branch your logic, and read `message` (or `reason`) for the human-readable description.

## What are the most common errors and how do I fix them?

The errors below cover the vast majority of failed requests. Each one comes with the response body the API returns and the fix.

### Why am I getting a 400 Bad Request?

A `400` means the request payload is malformed or missing a required parameter:

```json theme={null}
{
  "message": "both company_domain and company_name are absent, input payload needs at least one of them"
}
```

**Fix**: Check that you're providing all required parameters for the endpoint. For example, [Find Single Email](/api-reference/email-finder/find-single) requires `fullname` plus either `company_domain` or `company_name`.

### Why am I getting a 401 Unauthorized?

A `401` means the API key is missing or invalid:

```json theme={null}
{
  "message": "This apikey is not valid"
}
```

**Fix**: Verify the API key is correct and sent in the `x-api-key` header (not `Authorization`). See [Authentication](/authentication) for how to pass the key on every request.

### Why am I getting a 402 Insufficient Credits?

A `402` means the account doesn't have enough credits to run the request:

```json theme={null}
{
  "reason": "Insufficient credits",
  "success": false
}
```

On **bulk** endpoints, the same `402` is returned with the standard `{ "message": "..." }` shape instead.

**Fix**: Check the credit balance on the [Dashboard](https://app.enrow.io) or via `GET /account/info`. Top up credits or upgrade the plan at [enrow.io/pricing](https://enrow.io/pricing). See [Credits & billing](/credits-billing) for how credits are consumed per endpoint.

### Why am I getting a 429 Rate Limit Exceeded?

A `429` means too many requests were sent in a short window:

```json theme={null}
{
  "message": "Too Many Requests"
}
```

**Fix**: Wait and retry with exponential backoff. See [Rate limits](/rate-limits) for the limits per plan.

### Why am I getting a 500 Internal Server Error?

A `500` means something failed on Enrow's side:

```json theme={null}
{
  "message": "An unexpected error occurred"
}
```

**Fix**: Retry the request with backoff. If the issue persists, contact [api@enrow.io](mailto:api@enrow.io).

## How do I handle errors in code?

Check the HTTP status before parsing the success body, then raise an error that carries the status code so your retry logic can branch on it. The `message` field holds the description for most errors, and `reason` holds it for single-endpoint `402` responses.

<CodeGroup>
  ```javascript Node.js theme={null}
  async function findEmail(contact) {
    const response = await fetch('https://api.enrow.io/email/find/single', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': process.env.ENROW_API_KEY
      },
      body: JSON.stringify(contact)
    });

    if (!response.ok) {
      const body = await response.json();
      const error = new Error(`Enrow API error ${response.status}: ${body.message || body.reason}`);
      error.status = response.status;
      throw error;
    }

    return await response.json();
  }
  ```

  ```python Python theme={null}
  import requests
  import os

  def find_email(contact):
      response = requests.post(
          "https://api.enrow.io/email/find/single",
          json=contact,
          headers={
              "Content-Type": "application/json",
              "x-api-key": os.getenv("ENROW_API_KEY")
          }
      )

      if not response.ok:
          error = response.json()
          message = error.get("message") or error.get("reason")
          raise Exception(f"Enrow API error {response.status_code}: {message}")

      return response.json()
  ```
</CodeGroup>

## How do I retry failed requests?

Retry only the errors that are worth retrying — `429` and `5xx` — and back off exponentially between attempts. Client errors in the `4xx` range (except `429`) signal a problem with the request itself, so retrying them won't help.

```javascript theme={null}
async function requestWithRetry(fn, maxRetries = 3, baseDelay = 1000) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      const isLastAttempt = attempt === maxRetries - 1;

      // Don't retry client errors (except rate limits)
      if (error.status >= 400 && error.status < 500 && error.status !== 429) {
        throw error;
      }

      if (isLastAttempt) throw error;

      const delay = baseDelay * Math.pow(2, attempt);

      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}
```

## Best practices

* **Don't retry 4xx errors** (except 429) — fix the request instead
* **Retry 429 errors** — back off with exponential delays before retrying
* **Retry 5xx errors** — with exponential backoff
* **Use webhooks** instead of polling to avoid hitting rate limits unnecessarily — see [How webhooks work](/how-webhooks-work)
* **Log errors with context** — include the endpoint, parameters, and timestamp for debugging

## FAQ

<AccordionGroup>
  <Accordion title="How do I tell a 402 apart on single vs bulk endpoints?">
    On single endpoints (email/phone find single, verify single), a `402` returns `{ "reason": "Insufficient credits", "success": false }`. On bulk endpoints, the same `402` returns the standard `{ "message": "..." }` shape. Reading both `message` and `reason` (as the code examples do) handles either case.
  </Accordion>

  <Accordion title="Which errors should I retry automatically?">
    Retry `429` (rate limit) and `5xx` (server) errors with exponential backoff. Do not retry other `4xx` errors such as `400`, `401`, or `402` — they indicate a problem with the request, the API key, or the credit balance that retrying alone won't fix.
  </Accordion>

  <Accordion title="Is there a retry_after header to tell me how long to wait?">
    No. Error responses never include `retry_after`, `error`, or `status` fields. Use your own exponential backoff between retries. See [Rate limits](/rate-limits) for the limits that apply to your plan.
  </Accordion>

  <Accordion title="Where do I read the error message in the response?">
    For most errors, the description is in the `message` field. For an insufficient-credit `402` on a single endpoint, it's in the `reason` field instead. Always branch your logic on the HTTP status code rather than parsing the message text.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Status codes" icon="list-check" href="/status-codes">
    The complete list of HTTP status codes and their meanings.
  </Card>

  <Card title="Rate limits" icon="gauge-high" href="/rate-limits">
    Understand the request limits per plan to avoid 429 errors.
  </Card>

  <Card title="Authentication" icon="key" href="/authentication">
    How to pass your API key in the x-api-key header.
  </Card>

  <Card title="Credits & billing" icon="coins" href="/credits-billing">
    See how credits are consumed for each endpoint.
  </Card>
</CardGroup>
