Wattfare / Docs
Dashboard
Getting started

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

PhaseWho actsWhat happens
RegisterYou, onceCreate an app, get a publishable key (browser) and a secret key (backend).
ConnectYour userThey open the consent popup, set a monthly cap, and approve.
InferenceYour backendai.model(...) proxies through Wattfare, metered against the cap.
MonitorBothYou 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.

request flow
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:

KeyHolderCan doLeak impact
pk_live_…The browserIdentify your app to the consent popupLow — it only names your app
sk_live_…Your backendMint sessions, run inference, read statusHigh — rotate immediately

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.

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.

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.

ConnectionStatus
interface ConnectionStatus {
  connected: boolean;
  limits: { monthlyUsd: number | null } | null;
  usage: { monthlyUsd: number };
  remainingUsd: number | null; // null = unlimited (dev)
}
FieldMeaning
connectedWhether the user has an active, funded connection to your app.
limits.monthlyUsdThe approved cap, or null for unlimited (dev only).
usage.monthlyUsdSpend so far in the current rolling month.
remainingUsdlimits − 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 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.