HTTP API
The raw REST API the SDK wraps. You rarely call this directly — the SDK handles auth, token caching, and error typing — but it's here for debugging, non-JS stacks, and custom integrations.
Authentication
Two bearer schemes, for two trust levels:
| Scheme | Header | Used by |
|---|---|---|
| Secret key | Authorization: Bearer sk_live_{appId}_{secret} | Server-to-server: sessions, status, chat completions |
| Session token | Authorization: Bearer {jwt} | Browser: the /connections endpoints (via the SDK) |
Session tokens are JWTs with a 10-minute TTL carrying the app id, your user id, and the requested
limits. Mint them with POST /sessions; the SDK caches and refreshes them for you.
POST /sessions
Mints a frontend session token. Requires secret-key auth.
POST /api/v1/sessions
Authorization: Bearer sk_live_{appId}_{secret}
Content-Type: application/json
{
"appUserId": "user_123",
"requestLimit": { "monthlyUsd": 20 } // optional
} 200 OK
{
"token": "eyJhbGciOi…", // short-lived JWT for the frontend
"expiresAt": 1718400000 // unix seconds (default TTL: 10 min)
} Rate limited per app via RL_SESSIONS — 120 requests/minute.
/connections
The consent surface, authed with a session token (not the secret key). The SDK's
connect(), status(), and disconnect() map onto these.
# Approve / update — body { monthlyUsd? }, returns ConnectionStatus
POST /api/v1/connections Authorization: Bearer {session-jwt}
# Read current status
GET /api/v1/connections Authorization: Bearer {session-jwt}
# Revoke
DELETE /api/v1/connections Authorization: Bearer {session-jwt} | Method | Body | Returns |
|---|---|---|
| POST | { monthlyUsd? } | ConnectionStatus — approve or update the cap |
| GET | — | ConnectionStatus |
| DELETE | — | 204 — revoke the connection |
GET /status
Server-side connection check, keyed by your user id. Requires secret-key auth.
GET /api/v1/status?appUserId=user_123
Authorization: Bearer sk_live_{appId}_{secret} 200 OK
{
"connected": true,
"limits": { "monthlyUsd": 10 },
"usage": { "monthlyUsd": 3.20 },
"remainingUsd": 6.80
} POST /chat/completions
The OpenAI-compatible inference proxy. Requires secret-key auth and the
x-wattfare-user header so Wattfare resolves the right connection and budget.
POST /api/v1/chat/completions
Authorization: Bearer sk_live_{appId}_{secret}
x-wattfare-user: user_123
Content-Type: application/json
{
"model": "openai/gpt-4o-mini",
"messages": [{ "role": "user", "content": "Hello" }],
"stream": true
}
Responds with the standard OpenAI chat-completion format — JSON, or an SSE stream when
"stream": true. Usage is metered automatically: cost is read from the provider's
usage object and stored per-user, per-period. The stream is teed, never buffered.
curl https://wattfare.com/api/v1/chat/completions \
-H "Authorization: Bearer $WATTFARE_SECRET_KEY" \
-H "x-wattfare-user: user_123" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [{ "role": "user", "content": "Say hi" }]
}' One-time grants
For apps without a user system, grants provide a simpler flow: the browser requests a
grantToken via the consent popup, and the backend redeems it with the
x-wattfare-grant header. See One-time grants
for endpoints and examples.
Errors
Every error responds with the same body shape and a meaningful status:
{ "error": { "code": "budget_exceeded", "message": "Spending cap reached for this period." } } | Status | code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed body or parameters. |
| 401 | auth | Missing/invalid secret key or session token. |
| 402 | not_connected | User hasn't connected a budget. |
| 402 | budget_exceeded | Monthly cap reached. |
| 402 | grant_invalid | The grant token is unknown, expired, fully used, or over its dollar cap. |
| 402 | funding_invalid | User's funding source rejected upstream (reconnect needed). |
| 429 | rate_limited | Per-user inference limit — 30 requests/min per app-user. |
| 502 | upstream | The model provider returned an error. |
Rate limits
| Limiter | Scope | Limit |
|---|---|---|
| RL_SESSIONS | per app | 120 / minute |
| RL_CHAT | per app + user | 30 / minute |