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

# Error Handling

> Handle API errors, implement retry strategies, and understand Zenoo's internal error classification.

# Error Handling

## HTTP Status Codes

| Status | Meaning                    | Your Action                                                |
| ------ | -------------------------- | ---------------------------------------------------------- |
| `200`  | Success                    | Process the response                                       |
| `204`  | No Content (pull endpoint) | Results still processing. Retry later.                     |
| `400`  | Bad Request                | Fix the request payload and resubmit                       |
| `401`  | Unauthorized               | Check your `X-API-KEY` header                              |
| `403`  | Forbidden                  | Your API key does not have access to this project          |
| `404`  | Not Found                  | Invalid project hash, endpoint path, or expired pull token |
| `429`  | Rate Limited               | Back off and respect the `Retry-After` header              |
| `500`  | Internal Server Error      | Retry with exponential backoff                             |
| `503`  | Service Unavailable        | Retry after 30 seconds                                     |

## Error Response Format

All errors return a consistent JSON structure:

```json theme={null}
{
  "error": "VALIDATION_ERROR",
  "message": "Missing required field: company_name",
  "request_id": "req-a1b2c3d4"
}
```

| Field        | Description                                                              |
| ------------ | ------------------------------------------------------------------------ |
| `error`      | Machine-readable error code. Use this for programmatic handling.         |
| `message`    | Human-readable description. Log it, but do not display to end users.     |
| `request_id` | Unique identifier for the request. Include this in all support requests. |

<Tip>
  Always log `request_id`. It allows Zenoo support to trace the exact request through internal systems.
</Tip>

## Error Codes

| Error Code            | HTTP Status | Description                                               |
| --------------------- | ----------- | --------------------------------------------------------- |
| `VALIDATION_ERROR`    | 400         | Missing or invalid required fields                        |
| `INVALID_JSON`        | 400         | Request body is not valid JSON                            |
| `UNAUTHORIZED`        | 401         | Missing or invalid API key                                |
| `FORBIDDEN`           | 403         | API key lacks access to this project                      |
| `NOT_FOUND`           | 404         | Invalid project hash or endpoint path                     |
| `TOKEN_EXPIRED`       | 404         | Pull token has expired (30-day lifetime)                  |
| `RATE_LIMITED`        | 429         | Too many requests. Check `Retry-After` header.            |
| `INTERNAL_ERROR`      | 500         | Unexpected server error                                   |
| `PROVIDER_ERROR`      | 502         | Upstream provider (registry, screening) returned an error |
| `SERVICE_UNAVAILABLE` | 503         | Service temporarily unavailable                           |

## Retry Strategy

### Exponential Backoff

For transient errors (5xx, timeouts), retry with increasing delays:

```
Attempt 1: Immediate
Attempt 2: Wait 5 seconds
Attempt 3: Wait 25 seconds
Attempt 4: Wait 60 seconds
Maximum: 4 attempts
```

<CodeGroup>
  ```javascript retry-client.js theme={null}
  async function callWithRetry(fn, maxRetries = 4) {
    const delays = [0, 5000, 25000, 60000];

    for (let attempt = 0; attempt < maxRetries; attempt++) {
      try {
        const response = await fn();

        if (response.status === 429) {
          const retryAfter = response.headers.get("Retry-After");
          const delay = retryAfter
            ? parseInt(retryAfter) * 1000
            : delays[attempt] * 2;
          await sleep(delay);
          continue;
        }

        if (response.status >= 500) {
          if (attempt < maxRetries - 1) {
            await sleep(delays[attempt]);
            continue;
          }
        }

        return response;
      } catch (error) {
        if (attempt === maxRetries - 1) throw error;
        await sleep(delays[attempt]);
      }
    }
  }

  function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
  ```

  ```python retry_client.py theme={null}
  import time
  import requests
  from requests.exceptions import RequestException

  def call_with_retry(fn, max_retries=4):
      delays = [0, 5, 25, 60]

      for attempt in range(max_retries):
          try:
              response = fn()

              if response.status_code == 429:
                  retry_after = response.headers.get('Retry-After')
                  delay = int(retry_after) if retry_after else delays[attempt] * 2
                  time.sleep(delay)
                  continue

              if response.status_code >= 500:
                  if attempt < max_retries - 1:
                      time.sleep(delays[attempt])
                      continue

              return response
          except RequestException:
              if attempt == max_retries - 1:
                  raise
              time.sleep(delays[attempt])
  ```
</CodeGroup>

### When NOT to Retry

| Error              | Why                                                                     |
| ------------------ | ----------------------------------------------------------------------- |
| `400` Bad Request  | Your request is malformed. Fix the payload first.                       |
| `401` Unauthorized | Your API key is wrong. Retrying will not fix it.                        |
| `403` Forbidden    | Your key lacks permissions. Contact Zenoo support.                      |
| `404` Not Found    | The resource does not exist. Verify your endpoint URL and project hash. |

<Warning>
  Never retry 4xx errors automatically. They indicate a problem with your request, not a transient server issue.
</Warning>

## Rate Limiting

<Note>
  When you exceed your project's rate limit, you receive a `429` response. The `Retry-After` header tells you how many seconds to wait. Always use this value instead of guessing.
</Note>

```json theme={null}
{
  "error": "RATE_LIMITED",
  "message": "Rate limit exceeded. Retry after 60 seconds.",
  "request_id": "req-a1b2c3d4"
}
```

**Handling rate limits:**

1. Read the `Retry-After` header value.
2. Pause requests for that duration.
3. Resume. If you hit the limit again, increase your backoff.

For high-volume integrations, implement a request queue. Smooth out bursts rather than sending requests as fast as possible. Monitor your request rate and contact Zenoo if you need higher limits.

## Zenoo Internal Error Classification

Zenoo classifies errors from upstream providers into three categories. This determines automatic retry behavior on the server side.

| Category         | Examples                            | Retry Behavior                      |
| ---------------- | ----------------------------------- | ----------------------------------- |
| **Transient**    | Timeouts, 5xx from providers        | Auto-retry with exponential backoff |
| **Permanent**    | 4xx, invalid data, entity not found | No retry. Check fails immediately.  |
| **Rate Limited** | 429 from providers                  | Extended backoff (2x multiplier)    |

### Automatic Retry Schedule

For async flows, Zenoo automatically retries transient provider failures on your behalf:

| Retry | Delay       | Cumulative Wait |
| ----- | ----------- | --------------- |
| 1st   | 5 minutes   | 5 minutes       |
| 2nd   | 25 minutes  | 30 minutes      |
| 3rd   | 125 minutes | \~2.5 hours     |

Maximum delay is capped at 240 minutes (4 hours). Rate-limited errors use a 2x multiplier on these delays.

After all retries are exhausted:

* The check is marked as `Failed`.
* A `check.failed` webhook is sent to your endpoint.
* An API Failure alert is created in the case management system.

<Info>
  You do not need to implement retry logic for provider failures. Zenoo handles it automatically.
</Info>

## Circuit Breaker

Zenoo protects upstream providers with a circuit breaker pattern:

```mermaid theme={null}
stateDiagram-v2
    [*] --> Closed
    Closed --> Open: failures > threshold
    Open --> HalfOpen: reset timeout
    HalfOpen --> Closed: success
    HalfOpen --> Open: failure
```

| State         | Behavior                                                                  |
| ------------- | ------------------------------------------------------------------------- |
| **Closed**    | Normal operation. Requests flow through to the provider.                  |
| **Open**      | Provider is down. Requests fail fast and are queued for retry.            |
| **Half-Open** | Recovery test. A single request is sent to check if the provider is back. |

This is transparent to your integration. You do not need to implement any special handling. When a provider's circuit breaker opens:

* Checks for that provider are queued automatically.
* Other providers continue operating normally.
* You receive results via webhook once the provider recovers.

From your perspective, the verification simply takes longer to complete.

## Timeout Handling

### Sync Requests

If your `X-SYNC-TIMEOUT` is reached before all checks complete:

* The response may contain partial results.
* A pull token is included in the response headers for retrieving full results later.
* Consider increasing the timeout or switching to async mode.

### Async Requests

The `/init` endpoint responds immediately with tokens, so connection timeouts are rare. If you experience them:

* Check your network connectivity.
* Verify the base URL and project hash.
* Contact Zenoo support if the issue persists.

## Monitoring Recommendations

1. **Track error rates** by endpoint and HTTP status code.
2. **Alert on 5xx spikes.** Sustained 5xx errors may indicate a provider outage or Zenoo infrastructure issue.
3. **Monitor webhook delivery.** Track delivery failures and retry rates.
4. **Log `request_id` for every API call.** This is essential for debugging with Zenoo support.
5. **Track response times.** Latency increases may indicate provider degradation.
6. **Monitor rate limit proximity.** Alert when you reach 80% of your limit to avoid hitting it during traffic spikes.

## Next Steps

* [Webhooks](/guides/webhooks) -- Handle webhook delivery failures and retries
* [Idempotency](/guides/idempotency) -- Prevent duplicate verifications
* [Result Delivery](/guides/result-delivery) -- Choose between push, pull, and ping+pull
* [Testing & Sandbox](/testing/staging-environment) -- Test error scenarios in staging
