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

# Handling credit exhaustion

> Detect low balance before you run out and degrade gracefully when you do.

Running out of credits mid-batch is disruptive, especially if your system doesn't notice until the error rate spikes. Catch it early, degrade gracefully, and route around it.

## Early detection

Poll your credit balance before starting work that will consume a lot:

```ts theme={null}
async function ensureCredits(needed: number) {
  const credits = await reader.getCredits();
  if (credits.balance < needed) {
    throw new Error(
      `Insufficient credits: need ${needed}, have ${credits.balance}. ` +
        `Resets at ${credits.resetAt}.`,
    );
  }
}

await ensureCredits(estimateBatchCost(urls));
const result = await reader.read({ urls });
```

This isn't foolproof (other requests on the same workspace can consume credits between your check and your actual `/v1/read` call), but for big batches it catches the obvious "you're nowhere near enough" case.

## Catching the 402

When `/v1/read` returns `insufficient_credits`:

```ts theme={null}
import { InsufficientCreditsError } from "@vakra-dev/reader-js";

try {
  await reader.read({ url });
} catch (err) {
  if (err instanceof InsufficientCreditsError) {
    console.error(`Need ${err.required}, have ${err.available}`);
    console.error(`Resets at ${err.resetAt}`);
    await pauseWorker();
    await alertOps("reader-credits-exhausted", {
      available: err.available,
      resetAt: err.resetAt,
    });
    return;
  }
  throw err;
}
```

## Low-credit webhook

Subscribe to `credit.low` to get notified before you run out:

```ts theme={null}
await reader.request("POST", "/v1/webhooks", {
  url: "https://your-app.example.com/hooks/reader",
  name: "Credit alerts",
  events: ["credit.low"],
  secret: process.env.READER_WEBHOOK_SECRET,
});
```

Reader fires this event when your balance drops below 10% of your monthly limit. That gives you runway to:

* Alert an operator
* Pause background workers
* Switch to a higher tier automatically (if you have a billing API)
* Queue further requests until the next reset

## Graceful degradation strategies

When you're out of credits, what should your app actually do?

* **Cached responses only.** Set `cache: true` (the default) and accept that anything not already cached is unavailable until reset. Cache hits are free.
* **Queue and defer.** Accept user requests, queue the scrapes, run them when credits come back.
* **Fail visibly to users.** Better than silent corruption; show a "service degraded" banner.
* **Switch to a cheaper mode.** Temporarily force `proxyMode: "standard"` instead of `premium` to cut the cost by up to 3x per page.

## Credit budget per worker

For sustained workloads, budget your credits like you budget memory: cap each worker's daily draw and alert when it exceeds.

```ts theme={null}
const DAILY_BUDGET = 1000;
let dailySpend = 0;

async function scrapeWithBudget(url: string) {
  if (dailySpend >= DAILY_BUDGET) {
    throw new Error("Daily budget exhausted");
  }

  const result = await reader.read({ url });
  // The actual cost is 1 or 3 depending on the resolved mode
  const cost = result.data.metadata?.proxyMode === "premium" ? 3 : 1;
  dailySpend += cost;

  return result;
}
```

## Next

* [Credits and billing](/home/concepts/credits-and-billing)
* [Cost estimation](/home/guides/production/cost-estimation)
* [Choosing a proxy mode](/home/guides/advanced/choosing-a-proxy-mode)
