wattfare
v0.2.0
Published
Connect your LLM inference budget to third-party apps. OAuth-like consent + OpenAI-compatible proxy.
Maintainers
Readme
wattfare
Connect your users' LLM inference budget to your app with an OAuth-like consent flow and an OpenAI-compatible proxy.
Your users approve a spending budget once (in a hosted consent popup), and your app makes model calls on their behalf — metered against that budget. The secret key never leaves your backend; the browser only ever holds a short-lived, scoped session token.
- Drop-in AI SDK provider —
ai.model("openai/gpt-4o-mini")returns a standard Vercel AI SDKLanguageModel. - No secrets in the browser — the frontend forwards a short-lived JWT minted by your backend; it never sees the secret key or asserts a raw user id.
- Framework-agnostic — a server SDK (any
Request → Responseruntime), a vanilla browser client, and a React binding.
Install
npm install wattfareai (Vercel AI SDK) and react are optional peer dependencies — install them
only if you use the inference helpers or the React binding.
Entry points
The package has no root export. Import the surface you need:
| Import | Use in | Purpose |
| --- | --- | --- |
| wattfare/server | your backend | secret key, mint session tokens, run inference |
| wattfare/client | the browser | open the consent popup, read status |
| wattfare/react | React apps | <WattfareProvider> + useWattfare() |
Server
Hold the secret key on your backend. Use it to (a) mint session tokens for the frontend and (b) make inference calls on a user's behalf.
import { Wattfare } from "wattfare/server";
import { generateText } from "ai";
const wf = new Wattfare({ secretKey: process.env.WATTFARE_SECRET_KEY! });
// Inference, scoped to one of *your* user ids:
const ai = wf.user("user_123");
const { text } = await generateText({
model: ai.model("openai/gpt-4o-mini"),
prompt: "Say hello.",
});
// Connection + budget snapshot (never throws on first run):
const status = await ai.status();
// { connected, limits, usage, remainingUsd }Minting session tokens
The browser exchanges a token (minted by your backend) for popup + status
access. sessionHandler turns that into a one-line route in any framework that
speaks Request → Response:
// Next.js — app/api/wattfare-token/route.ts
export const POST = wf.sessionHandler(
async (req) => getUserId(req), // your auth → app user id, or null → 401
{ requestLimit: { monthlyUsd: 20 } }, // optional budget the user will approve
);Or call createSession directly:
const { token, expiresAt } = await wf.createSession("user_123", {
requestLimit: { monthlyUsd: 20 },
});React
Wrap your app and drive the connect flow with a hook. Point session at the
backend route above.
import { WattfareProvider, useWattfare } from "wattfare/react";
function App() {
return (
<WattfareProvider
publishableKey={import.meta.env.PUBLIC_WATTFARE_KEY}
session="/api/wattfare-token"
>
<Budget />
</WattfareProvider>
);
}
function Budget() {
const { connected, connect, disconnect, remainingUsd, loading } = useWattfare();
if (loading) return <p>…</p>;
return connected ? (
<div>
<p>Remaining: {remainingUsd === null ? "unlimited" : `$${remainingUsd}`}</p>
<button onClick={disconnect}>Disconnect</button>
</div>
) : (
<button onClick={connect}>Connect AI budget</button>
);
}useWattfare() exposes connect, disconnect, refresh, connected,
remainingUsd, status, loading, and error.
Vanilla client
No framework? Use the browser client directly. Call connect() straight from
a click handler — opening the popup must happen inside the user gesture or
popup blockers will kill it.
import { createWattfare } from "wattfare/client";
const wf = createWattfare({
publishableKey: "pk_live_…",
session: "/api/wattfare-token", // or a () => Promise<token> function
});
button.onclick = () => wf.connect();Errors
All SDK errors extend WattfareError. Helpers and typed subclasses are exported
from every entry point:
import { isBudgetExceeded, isNotConnected, WattfareError } from "wattfare/server";
try {
await generateText({ model: ai.model("openai/gpt-4o-mini"), prompt });
} catch (err) {
if (isNotConnected(err)) {/* prompt the user to connect */}
else if (isBudgetExceeded(err)) {/* show "budget reached" */}
else throw err;
}License
MIT
