Wattfare / Docs
Dashboard
Getting started

One-time grants

One-time grants let users approve a fixed number of AI requests without signing in or creating a persistent connection — ideal for single-purpose tools like file converters, text rewriters, or any feature where users interact once and move on.

When to use grants

The standard Wattfare flow — connect() → session → metered inference — assumes your app has a user system and wants an ongoing budget relationship. Grants skip all of that.

Use grants when…Use connections when…
Your app has no auth / user accounts.You have user ids and want ongoing metered access.
The user will interact once or twice (e.g. convert a file, rewrite a paragraph).The user will make many requests over days or weeks.
You want the simplest possible integration.You need status tracking, budget bars, and revocation.
You want per-action consent: 'allow 3 requests for this task'.You want a monthly spending cap the user manages.

How it works

Two steps instead of four. No session token, no user id, no persistent connection:

grant flow
Your App (browser)               Wattfare popup              Your Server
      │                                  │                          │
  1   ├─ requestGrant({ requests: 3 }) ─►│ user sees request count  │
      │                                  │ + optional $ cap         │
      │◄─ { grantToken, expiresAt } ─────┤ user approves            │
      │                                  │                          │
  2   ├─ POST /api/chat  { grantToken } ────────────────────────────►│
      │                                  │         wf.grant(token)  │
      │                                  │         ai.model(…)      │
      │◄─ response ─────────────────────────────────────────────────┤
      │   (grant: 2 requests remaining)  │                          │
  1. Your frontend calls requestGrant({ requests, maxUsd? }) — the consent popup opens and the user approves a fixed number of requests (plus an optional dollar cap).
  2. On approval, the popup returns a grantToken. Your frontend forwards it to your backend.
  3. Your backend calls wf.grant(grantToken) to get a WattfareGrant handle, then uses ai.model(...) exactly like a connected user — but each call decrements the grant's remaining count.
  4. Once the request count (or dollar cap) is exhausted, further calls throw WattfareGrantError (grant_invalid).

Browser: requestGrant()

Available on both the vanilla client (wattfare/client) and the React hook (wattfare/react). Opens the consent popup and resolves with a GrantResult, or null if the user denies.

wattfare/client
import { createWattfare } from "wattfare/client";

const wf = createWattfare({
  publishableKey: "pk_live_…",
  session: "/api/wattfare-token",
});

// Must be called from a user gesture (click/tap).
convertBtn.onclick = async () => {
  const result = await wf.requestGrant({ requests: 1, maxUsd: 0.50 });
  if (!result) return; // user denied or closed the popup

  // Forward the grant token to your backend
  const res = await fetch("/api/convert", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ grantToken: result.grantToken, file: selectedFile }),
  });
};
OptionTypeDescription
requestsnumberHow many inference calls to authorize (hard limit).
maxUsdnumber | nullOptional dollar backstop. Omit for request-count-only grants.
Result fieldTypeDescription
grantTokenstringOne-time token. Forward to your backend to redeem.
expiresAtnumberToken expiry (unix seconds). Default: 10 minutes.
requestsnumberNumber of authorized requests.

React: useWattfare().requestGrant()

The requestGrant function from the useWattfare() hook works identically. It does not affect connection state — connected, remainingUsd, and other hook values stay unchanged.

wattfare/react
import { useWattfare } from "wattfare/react";

function ConvertButton() {
  const { requestGrant } = useWattfare();

  async function handleClick() {
    const result = await requestGrant({ requests: 1, maxUsd: 0.50 });
    if (!result) return; // user denied

    await fetch("/api/convert", {
      method: "POST",
      body: JSON.stringify({ grantToken: result.grantToken, file }),
    });
  }

  return <button onClick={handleClick}>✨ Convert with AI</button>;
}

Server: wf.grant()

Redeems the grant token. Returns a WattfareGrant handle whose model() method works exactly like WattfareUser.model() — it's a standard AI SDK provider. No user id required.

wattfare/server
import { Wattfare } from "wattfare/server";
import { generateText } from "ai";

const wf = new Wattfare({ secretKey: process.env.WATTFARE_SECRET_KEY! });

export async function POST(req: Request) {
  const { grantToken, file } = await req.json();

  // Redeem the grant — no user id needed.
  const ai = wf.grant(grantToken);

  const { text } = await generateText({
    model: ai.model("anthropic/claude-sonnet-4"),
    prompt: `Convert this document to markdown: ${file}`,
  });

  return Response.json({ result: text });
  // The grant's request count was decremented by 1.
}
MethodReturnsDescription
wf.grant(grantToken)WattfareGrantRedeem a grant. Throws WattfareAuthError if the token is empty.
ai.model(modelId)LanguageModelV3AI-SDK-compatible model. Each call decrements the grant's remaining request count.

HTTP API

The SDK handles this for you, but here's the raw API for debugging or non-JS stacks.

POST /grants

Creates a grant. Authed with a session token (the consent popup calls this on approval).

request
POST /api/v1/grants
Authorization: Bearer {session-jwt}
Content-Type: application/json

{
  "requests": 3,       // how many inference calls to authorize
  "maxUsd": 1.50       // optional dollar backstop
}
response
200 OK
{
  "grantToken": "wfg_abc123…",   // one-time token for the backend
  "expiresAt": 1718400000,       // unix seconds
  "requests": 3                  // authorized request count
}

Redeeming via /chat/completions

Send the x-wattfare-grant header instead of x-wattfare-user. Each successful call decrements the grant's remaining count. When exhausted (or the dollar cap is hit), the proxy responds 402 grant_invalid.

grant-based inference
POST /api/v1/chat/completions
Authorization: Bearer sk_live_{appId}_{secret}
x-wattfare-grant: wfg_abc123…
Content-Type: application/json

{
  "model": "openai/gpt-4o-mini",
  "messages": [{ "role": "user", "content": "Hello" }]
}

Error handling

Grant errors use the same typed error model as the rest of Wattfare. The specific class is WattfareGrantError with code grant_invalid.

handling grant errors
import { toWattfareError } from "wattfare/server";

try {
  await generateText({ model: ai.model("openai/gpt-4o-mini"), prompt });
} catch (err) {
  const wf = toWattfareError(err);
  if (wf?.code === "grant_invalid") {
    // Grant is expired, fully used, or over its dollar cap.
    // Ask the user to approve a new grant.
    return Response.json({ error: "grant_used" }, { status: 402 });
  }
  throw err;
}
ConditionMeaningWhat to do
grant_invalidThe token is unknown, expired, fully used, or over its dollar cap.Show a "grant used up" message and offer to request a new one.