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

# Fail open

> Keep your product running when Ledger can't answer.

`check` sits in front of every metered action in your product. If Ledger can't answer it, the SDKs **fail open**: the check is allowed and the usage is queued for later, so an outage on our side never stops your customers. Business answers, such as an insufficient balance or an unknown customer, are never failed open.

Fail open is on by default in the TypeScript and Python SDKs, for `checkCredits` / `check_credits` and `trackUsage` / `track_usage` only. Every other method throws as usual. Calling the API directly, for example with cURL, does none of this.

## When it fails open

| Ledger… | `reason` |
| - | - |
| can't be reached: DNS, a refused or reset connection | `network_error` |
| doesn't answer within the timeout (2 seconds by default) | `timeout` |
| answers `5xx` | `server_error` |
| answers `429`, after any retries you configured | `rate_limited` |

These are still thrown, exactly as without fail open:

* `400 invalid_request`, `401 unauthorized`, `404 customer_not_found` and `404 feature_not_found`.
* Track's `409 insufficient_credits`, `409 overage_cap_reached` and `409 request_id_conflict`.
* A `429` after a `401`, so a wrong API key is never hidden.
* Your own aborted request.

A check that Ledger answers with `allowed: false` stays `allowed: false`.

## What you get back

Every result from these two methods has `failedOpen`. When it is `true`, the result also has a `reason`, and the numbers Ledger would have sent are `null` because they are unknown.

```typescript TypeScript theme={"system"}
const decision = await client.checkCredits({ customerId: "user_123", featureId: "api_call" });
// Ledger answered:   { allowed: true, availableCredits: 99, creditCost: 1, failedOpen: false }
// Ledger was down:   { allowed: true, availableCredits: null, creditCost: null, failedOpen: true, reason: "timeout" }
if (decision.allowed) {
  // Run the action, then record it.
  const tracked = await client.trackUsage({ customerId: "user_123", featureId: "api_call", requestId: "operation_123" });
  if (tracked.failedOpen) {
    // tracked.queued: the usage will be replayed with the same requestId.
  }
}
```

```python Python theme={"system"}
decision = client.check_credits(customer_id="user_123", feature_id="api_call")
# Ledger answered:   {"allowed": True, "availableCredits": 99, "creditCost": 1, "failedOpen": False}
# Ledger was down:   {"allowed": True, "availableCredits": None, "creditCost": None, "failedOpen": True, "reason": "timeout"}
if decision["allowed"]:
    tracked = client.track_usage(customer_id="user_123", feature_id="api_call", request_id="operation_123")
    if tracked["failedOpen"]:
        pass  # tracked["queued"]: the usage will be replayed with the same request_id.
```

## Usage is replayed, not lost

A track that fails open is kept in memory and sent again in the background with the **same** `request_id`, so it is charged once however many times it is sent ([Record usage](https://docs.integrateledger.com/api-reference/usage/track.md)). Retries start after a second and back off to every 30 seconds while Ledger stays down.

Replayed usage is charged at the balance when it lands. It is dropped, and reported to your callback, when:

* Ledger answers it with a business error, such as `409 insufficient_credits` because the balance ran out meanwhile (`rejected`);
* it is still queued an hour later (`expired`);
* the client already holds 1,000 queued usages (`queue_full`).

The queue lives in your process. Before a serverless function returns or a worker shuts down, flush it:

```typescript TypeScript theme={"system"}
const { delivered, dropped, remaining } = await client.flushQueuedTracks();
```

```python Python theme={"system"}
result = client.flush_queued_tracks()  # {"delivered": 1, "dropped": 0, "remaining": 0}
client.close()  # stops the background timer
```

## Logging fail-open events

By default the SDKs write a short warning, at most every 10 seconds for each kind of event: `console.warn` in TypeScript, the `integrateledger` logger in Python. Pass a callback to send events to your own logging or alerting:

```typescript TypeScript theme={"system"}
import { Ledger } from "integrateledger";

const client = new Ledger({
  apiKey: process.env.LEDGER_API_KEY ?? "",
  failOpen: {
    onFailOpen: (event) => logger.warn(event, "ledger fail open"),
  },
});
```

```python Python theme={"system"}
import logging
import os
from integrateledger import FailOpen, ledger

log = logging.getLogger("billing")
client = ledger(
    api_key=os.environ["LEDGER_API_KEY"],
    fail_open=FailOpen(on_fail_open=lambda event: log.warning("ledger fail open: %s", event)),
)
```

| Event | When |
| - | - |
| `check_failed_open` | A check returned the allowed fallback |
| `track_queued` | A track failed open and its usage was queued |
| `track_delivered` | Queued usage reached Ledger; `attempts` says how many replays it took |
| `track_dropped` | Queued usage won't be replayed; `cause` is `rejected`, `expired` or `queue_full` |

Events carry the customer, feature and request IDs, the `reason` or `cause`, and the SDK error. A callback that throws is ignored.

## Settings

| TypeScript `failOpen` | Python `FailOpen` | Default |
| - | - | - |
| `check` | `check` | `true`: allow checks Ledger can't answer |
| `track` | `track` | `true`: queue usage Ledger can't record |
| `timeoutMs` | `timeout` (seconds) | 2,000 ms: per attempt, for check and track only. A per-call `timeoutMs` still wins. |
| `onFailOpen` | `on_fail_open` | A throttled warning |

`failOpen: false` (Python `fail_open=False`) turns it off: both methods throw on an outage, as before.

```typescript TypeScript theme={"system"}
const strict = new Ledger({ apiKey: process.env.LEDGER_API_KEY ?? "", failOpen: false });
const checksOnly = new Ledger({ apiKey: process.env.LEDGER_API_KEY ?? "", failOpen: { track: false } });
```

```python Python theme={"system"}
strict = ledger(api_key=os.environ["LEDGER_API_KEY"], fail_open=False)
checks_only = ledger(api_key=os.environ["LEDGER_API_KEY"], fail_open=FailOpen(track=False))
```


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