URL: https://wattfare.com/docs/errors
# Error handling

> Every SDK error extends WattfareError. The “not connected” case is the expected first-run path, so it's modeled as a branch — not a crash. Helpers recover typed errors even when the AI SDK wraps them.

## Error classes

Typed subclasses are exported from every entry point (`server`, `client`, `react`).

| Class | code | HTTP | When it happens |
| --- | --- | --- | --- |
| WattfareNotConnectedError | not_connected | 402 | The user hasn't connected a budget to your app yet. |
| WattfareBudgetExceededError | budget_exceeded | 402 | Their monthly cap for this period is reached. |
| WattfareFundingError | funding_invalid | 402 | Their funding key was rejected upstream (revoked / out of credit). They must reconnect. |
| WattfareRateLimitError | rate_limited | 429 | Too many requests. Back off and retry. |
| WattfareGrantError | grant_invalid | 402 | The one-time access grant is unknown, expired, fully used, or over its dollar cap. |
| WattfareAuthError | auth | 401 | Invalid or missing credentials (secret key, session token…). |
| WattfareNetworkError | network | 0 | Couldn't reach the Wattfare service at all. |

A generic `WattfareError` also covers `invalid_request`, `upstream` (the model provider failed), and `unknown`.

```ts
// Every WattfareError serialises to this shape over the wire:
{ "error": { "code": "budget_exceeded", "message": "Spending cap reached for this period." } }

// And carries:
err.code    // WattfareErrorCode
err.status  // HTTP status it maps to
err.toJSON()
```

## Helper functions

Reach for these instead of `instanceof`. They see through the AI SDK's `APICallError` wrapper, which would otherwise hide the real code.

| Function | Returns | Use it to… |
| --- | --- | --- |
| toWattfareError(err) | WattfareError \| null | Recover a typed error from anything. `null` when unrelated to Wattfare. |
| isBudgetExceeded(err) | boolean | Detect a reached cap (even AI-SDK-wrapped). |
| isNotConnected(err) | boolean | Detect “user must (re)connect” — matches both `not_connected` and `funding_invalid`. |

> **Why plain instanceof fails**
>
> When you call a model through `ai.model(...)`, the AI SDK catches Wattfare's HTTP error and re-throws its own `APICallError` wrapping the response body. So `err instanceof WattfareBudgetExceededError` is `false`. Always use `toWattfareError` / the `is*` guards around inference calls.

## The canonical pattern

Handle the two actionable cases first, then fall back to the typed error's own status:

```ts
import { isNotConnected, isBudgetExceeded, toWattfareError } from "wattfare/server";

try {
  const result = await generateText({
    model: ai.model("anthropic/claude-sonnet-4"),
    prompt,
  });
  return Response.json({ text: result.text });
} catch (err) {
  if (isNotConnected(err))    return Response.json({ error: "connect_required" }, { status: 402 });
  if (isBudgetExceeded(err))  return Response.json({ error: "budget_reached" },   { status: 402 });

  const wf = toWattfareError(err);
  if (wf) return Response.json(wf.toJSON(), { status: wf.status });
  throw err; // genuinely unrelated — let it bubble
}
```

## Branching on code

When you need finer control, read `code` off the recovered error:

```ts
import { toWattfareError } from "wattfare/server";

try {
  await generateText({ model: ai.model("openai/gpt-4o-mini"), prompt });
} catch (err) {
  // err is the AI SDK's APICallError — a plain instanceof check would miss it.
  const wf = toWattfareError(err);
  if (wf?.code === "budget_exceeded") return askUserToRaiseCap();
  if (wf?.code === "funding_invalid") return askUserToReconnect();
  throw err;
}
```

## What to show users

| Condition | User-facing action |
| --- | --- |
| `not_connected` | Show the “Connect AI budget” button. |
| `funding_invalid` | Same button, with a “reconnect — your funding needs attention” note. |
| `budget_exceeded` | Show usage vs. cap; link them to raise the cap in their dashboard. |
| `grant_invalid` | Show a "Your access grant has been used up" message and offer to request a new one. |
| `rate_limited` | Back off (exponential) or show a brief “slow down” message. |
| `network` / `upstream` | Retry with backoff; surface a transient-error toast. |
| `auth` | A bug on your side — your secret key or session is wrong. Log and alert. |

> **Tip**
>
> `isNotConnected` intentionally returns `true` for `funding_invalid` too, so a single check covers “the user needs to take action before this can work.”
