URL: https://wattfare.com/docs/concepts
# How it works

> The consent flow, the moving parts, and the guarantees behind them. Read this once and the SDK reference will feel obvious.

Wattfare sits between your app and the model provider. Your app never holds a user's funding source, and your users never hold a Wattfare account — connections are keyed by **your** user ids. This page walks the whole loop end to end.

## The four phases

| Phase | Who acts | What happens |
| --- | --- | --- |
| **Register** | You, once | Create an app, get a publishable key (browser) and a secret key (backend). |
| **Connect** | Your user | They open the consent popup, set a monthly cap, and approve. |
| **Inference** | Your backend | `ai.model(...)` proxies through Wattfare, metered against the cap. |
| **Monitor** | Both | You read `remainingUsd`; users can revoke from their dashboard. |

## End-to-end sequence

Three round-trips that matter: minting a session, the consent popup, and the metered inference proxy. Everything else is detail.

```text
Your App (browser)        Your Server           Wattfare API           OpenRouter
      │                        │                      │                      │
  1   ├─ POST /wattfare-token ─►│                      │                      │
      │                        ├─ POST /v1/sessions ─►│                      │
      │                        │◄─ { token, exp } ────┤                      │
      │◄─ { token } ───────────┤                      │                      │
      │                        │                      │                      │
  2   ├─ connect() ── popup ───────────────────────► consent screen          │
      │◄─ approved (postMessage) ───────────────────── user sets a cap       │
      │                        │                      │                      │
  3   ├─ POST /api/chat ──────►│                      │                      │
      │                        ├─ POST /v1/chat/* ───►├─ proxy (user's key)─►│
      │                        │                      │◄─ completion ────────┤
      │                        │◄─ stream ────────────┤   (usage metered)    │
      │◄─ stream ──────────────┤                      │                      │
```

## Keys and what they unlock

Two keys, two trust levels. Keeping them straight is the whole security model:

| Key | Holder | Can do | Leak impact |
| --- | --- | --- | --- |
| pk_live_… | The browser | Identify your app to the consent popup | Low — it only names your app |
| sk_live_… | Your backend | Mint sessions, run inference, read status | High — rotate immediately |

> **Why a session token at all?**
>
> The browser needs to open the consent popup and read status, but it can't be trusted with the secret key or to assert “I am user 123”. So your backend mints a short-lived JWT (10-minute TTL) that encodes the app id, your user id, and the requested cap. The browser only ever forwards that token.

## Sessions

A session token is a signed JWT your backend creates with `createSession()` (or the `sessionHandler()` wrapper). It carries:

- `appId` — derived from your secret key, so the popup knows who's asking.
- `appUserId` — your own user id, the join key for the connection.
- `limits` — the monthly cap you want the user to approve.
- `exp` — expiry, 10 minutes out by default.

The client SDK caches the token until ~30 seconds before expiry, so reading status and opening the popup don't each hit your backend. The token rides in the popup's URL *fragment*, never the query string — fragments aren't sent to servers, logged, or kept in referrers.

## The consent popup

`connect()` opens a popup on Wattfare's domain. The user sees which app is asking, sets a monthly spending cap, and connects their funding source. On approval, the popup sends the result back via `postMessage` with strict origin verification:

- The popup asks “who opened me?”; the SDK replies, stamping your page's origin on the answer.
- The popup compares that browser-set origin against your app's registered domain before allowing approval — an unforgeable proof, so a random site can't impersonate your app.
- If the user closes the popup, the SDK treats it as a denial and returns the not-connected status.

## Inference and metering

`ai.model(modelId)` returns an OpenAI-compatible AI SDK model. Each request carries an `x-wattfare-user` header so the proxy resolves the right connection. On every call, Wattfare:

1. Verifies your secret key and resolves the user's connection + funding.
2. Checks the cap — if spend ≥ cap, it throws `budget_exceeded` before the request leaves.
3. Decrypts the user's funding key per request (so revocation is always live) and forwards to OpenRouter.
4. Tees the response: bytes stream to you untouched while a parallel reader extracts the final `usage` and records cost. The response is never buffered.

> **Streaming is first-class**
>
> Because metering happens on a teed branch of the stream, `streamText().toTextStreamResponse()` works exactly as you'd expect — no added latency, no buffering, full cost accounting.

## Connection status

Both `ai.status()` (server) and `wf.status()` (client) return the same shape. It never throws for “not connected” — that's the expected first-run state, modeled as a value, not an exception.

```ts
interface ConnectionStatus {
  connected: boolean;
  limits: { monthlyUsd: number | null } | null;
  usage: { monthlyUsd: number };
  remainingUsd: number | null; // null = unlimited (dev)
}
```

| Field | Meaning |
| --- | --- |
| connected | Whether the user has an active, funded connection to your app. |
| limits.monthlyUsd | The approved cap, or `null` for unlimited (dev only). |
| usage.monthlyUsd | Spend so far in the current rolling month. |
| remainingUsd | `limits − usage`, or `null` when unlimited. |

## One-time grants

Not every app has a user system. **One-time grants** let users approve a fixed number of AI requests without creating a persistent connection — perfect for single-purpose tools like file converters or text rewriters. See the [One-time grants](https://wattfare.com/docs/grants) page for the full flow.

## The guarantees

- **Secret stays server-side.** The browser only holds a scoped, short-lived JWT.
- **Your ids, not ours.** No Wattfare account for your users; state is keyed by `appId:appUserId`.
- **Caps enforced upstream.** Budget is checked inside Wattfare before any request reaches the model provider.
- **Revocable and live.** A user can disconnect anytime; the funding key is re-checked on every request.
- **We never see prompts… for billing.** Cost is read from the provider's usage object; the proxy doesn't store prompt content.
- **Grants are disposable.** One-time tokens are request-counted and optionally dollar-capped. No lingering access after the grant is spent.
