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

# Retries and reliability

> When it is safe to send a request again, and how to do it without paying twice.

Finowl never retries a lookup for you. Every attempt that starts a lookup is charged, so automatic retries could double your costs without you noticing. You stay in control of when to try again.

## The rules

<CardGroup cols={3}>
  <Card title="Safe to resend" icon="circle-check" color="#176b4c">
    `rate_limited`

    `quota_exceeded` (after topping up)

    `provider_not_configured`

    Network errors before a connection was made
  </Card>

  <Card title="Resend with care" icon="circle-exclamation" color="#b7791f">
    `provider_rate_limited`

    `provider_unavailable`

    `provider_response_unconfirmed`

    `accounting_response_unconfirmed`

    Timeouts on your side
  </Card>

  <Card title="Never resend as is" icon="circle-xmark" color="#c53030">
    `invalid_input`

    `unauthorized`

    `json_required`

    `payload_too_large`

    `not_found`
  </Card>
</CardGroup>

**Safe to resend** errors were rejected before any work happened, so trying again costs nothing extra.

**Resend with care** errors happened after the lookup was sent, so the first attempt was already charged. Resending is fine when you need the answer, but do it once, after a delay, and not in a tight loop.

**Never resend** errors will fail the same way every time. Fix the request or the key first.

## A retry policy that works

* Retry at most **2 times**.
* Wait for `Retry-After` when it is present, otherwise back off exponentially: 2 seconds, then 8 seconds, with random jitter.
* Only retry "resend with care" errors for requests a user is actively waiting on, such as a password check at sign up. For background jobs, record the failure and move on.

```javascript theme={null}
const SAFE = new Set(["rate_limited", "provider_not_configured"]);
const CAREFUL = new Set([
  "provider_rate_limited",
  "provider_unavailable",
  "provider_response_unconfirmed",
  "accounting_response_unconfirmed",
]);

async function withRetries(call, { interactive = false } = {}) {
  for (let attempt = 0; ; attempt++) {
    try {
      return await call();
    } catch (error) {
      const retryable = SAFE.has(error.code) || (interactive && CAREFUL.has(error.code));
      if (!retryable || attempt >= 2) throw error;

      const backoff = (error.retryAfter ?? 2 * 4 ** attempt) * 1000;
      await new Promise((resolve) => setTimeout(resolve, backoff + Math.random() * 500));
    }
  }
}

// Usage, with the finowl() helper from the Errors page:
const result = await withRetries(
  () => finowl("/v1/breaches/password", { password }),
  { interactive: true },
);
```

## Batches

Never resend a whole batch because some items failed. The successful items were charged and their results are in the response.

1. Save every item that has `data`.
2. Collect the items that have `error`.
3. If an item's error has `action: "inspect_billing_before_retry"`, check your usage before deciding.
4. Send the failed items again in a new batch, once, if you still need them.

If `billing_status` is `unconfirmed`, do not assume failed items were free.

## Timeouts

Most lookups finish in well under a second, but Finowl waits up to 35 seconds for a slow lookup before answering with `504 provider_response_unconfirmed`. Set your HTTP client timeout to **40 seconds** so that you receive Finowl's answer instead of cutting the connection yourself.

If your own timeout fires first, the request may still have run and been charged. Treat it like `provider_response_unconfirmed`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.