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

# Retry Logic

> Retry strategy, timeout guidance, and failure handling for the Public Records API

## When to Retry

| Status Code     | Retry? | Action                                                                    |
| --------------- | ------ | ------------------------------------------------------------------------- |
| `400`, `422`    | No     | Fix the request — these are request errors (bad payload, missing fields)  |
| `401`           | Once   | Refresh your JWT token via `/auth/token`, then retry the original request |
| `500`, `503`    | Yes    | Transient server error — retry with exponential backoff                   |
| Network timeout | Yes    | Retry with exponential backoff, check `/health` before retrying           |

## Retry Strategy

Use exponential backoff for transient errors (500, 503). Do **not** retry request errors (400, 401, 422) — fix the request first.

| Attempt   | Wait      | Cumulative |
| --------- | --------- | ---------- |
| 1st retry | 1 second  | 1s         |
| 2nd retry | 2 seconds | 3s         |
| 3rd retry | 4 seconds | 7s         |

**Max retries:** 3

## Timeout Guidance

| Request Type | Recommended Timeout |
| ------------ | ------------------- |
| `/evaluate`  | 120 seconds         |
| `/health`    | 10 seconds          |

## Fallback: Route to Manual Review

If the API is unresponsive after all retries, route the record to your manual review queue. Before retrying, call `/health` to confirm service availability.

## Code Examples

<CodeGroup>
  ```python Python theme={null}
  import requests
  import time

  def evaluate_with_retry(base_url, api_key, record_data, max_retries=3):
      token = get_token(base_url, api_key)

      for attempt in range(max_retries):
          try:
              response = requests.post(
                  f"{base_url}/evaluate",
                  headers={
                      "Authorization": f"Bearer {token}",
                      "Content-Type": "application/json"
                  },
                  json={"record": record_data},
                  timeout=120
              )

              if response.status_code == 200:
                  return response.json()

              if response.status_code == 206:
                  return response.json()

              if response.status_code == 401:
                  token = get_token(base_url, api_key)
                  continue

              if response.status_code >= 400 and response.status_code < 500:
                  raise Exception(f"Client error {response.status_code}: {response.text}")

              if response.status_code >= 500:
                  if attempt < max_retries - 1:
                      time.sleep(2 ** attempt)
                      continue
                  raise Exception(f"Server error after {max_retries} attempts")

          except requests.exceptions.Timeout:
              if attempt < max_retries - 1:
                  health = requests.get(f"{base_url}/health", timeout=10)
                  if health.status_code != 200:
                      time.sleep(2 ** attempt)
                      continue
              raise

      raise Exception("Max retries exceeded — route to manual review queue")


  def get_token(base_url, api_key):
      response = requests.post(
          f"{base_url}/auth/token",
          headers={"X-API-Key": api_key}
      )
      response.raise_for_status()
      return response.json()["access_token"]
  ```

  ```javascript JavaScript theme={null}
  async function evaluateWithRetry(baseUrl, getToken, recordData, maxRetries = 3) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      const token = await getToken();
      try {
        const response = await fetch(`${baseUrl}/evaluate`, {
          method: "POST",
          headers: {
            Authorization: `Bearer ${token}`,
            "Content-Type": "application/json"
          },
          body: JSON.stringify({ record: recordData }),
          signal: AbortSignal.timeout(120_000)
        });

        if (response.ok || response.status === 206) {
          return await response.json();
        }

        if (response.status === 401) {
          await getToken({ forceRefresh: true });
          continue;
        }

        if (response.status >= 400 && response.status < 500) {
          throw new Error(`Client error ${response.status}: ${await response.text()}`);
        }

        if (attempt < maxRetries - 1) {
          await new Promise(r => setTimeout(r, 2 ** attempt * 1000));
          continue;
        }

        throw new Error(`Server error after ${maxRetries} attempts`);
      } catch (err) {
        if (err.name === "TimeoutError" && attempt < maxRetries - 1) {
          const health = await fetch(`${baseUrl}/health`).catch(() => null);
          if (!health?.ok) {
            await new Promise(r => setTimeout(r, 2 ** attempt * 1000));
            continue;
          }
        }
        if (attempt === maxRetries - 1) throw err;
      }
    }
  }
  ```
</CodeGroup>

## Related

* [Error Handling](/error-handling) — HTTP status codes and error response formats
* [Troubleshooting](/troubleshooting) — Solutions for common issues
* [Environments](/environments) — Timeout and response time guidance
