Docs

Wrap your agent’s fetch, watch spend land in the union view, and freeze out of band. Three moving parts.

Concepts

Rails
Every spend event belongs to a rail: LLM APIs, data vendors, cloud/metered, payments, crypto/x402. The union view is the sum across all of them, per agent.
Local enforcement
The SDK holds a policy snapshot and evaluates it in-process. There is no authorize round-trip before a paid call, so SpendWall adds no latency to your agent.
Out-of-band freeze
Freezing an agent revokes its ingest key. The SDK notices on its next snapshot poll (default 30s) and blocks locally. SpendWall never sits in your payment path.
Custody-free
SpendWall observes and controls; it never holds funds or credentials for the vendors your agents pay.

SDK quickstart

Install the SDK and grab an ingest key from Settings → API keys.

terminal
# npm / pnpm / bunpnpm add @spendwall/sdk
agent.ts
import { createSpendwall } from "@spendwall/sdk"const sw = createSpendwall({apiKey: process.env.SPENDWALL_KEY!,agentId: "research-crawler",baseUrl: "https://spendwall.io/api/v1",})// 1. drop-in fetch: records spend, enforces locallyconst res = await sw.fetch(url, opts)// 2. or record spend you priced yourselfsw.recordSpend({ rail: "data", provider: "firecrawl", amountCents: 4 })// 3. flush on shutdownawait sw.close()

When you freeze the agent from the dashboard, sw.fetch throws SpendwallFrozenError on the next call after the snapshot refresh. Hard policy blocks throw SpendwallPolicyError.

API reference

Ingest endpoints authenticate with your key: Authorization: Bearer sw_live_…

POST/api/v1/event

Record one spend event, or a batch.

{ "agentId": "…", "rail": "llm", "provider": "openai",
  "endpoint": "https://api.openai.com/v1/chat/completions",
  "amountCents": 42, "outcome": "success" }
{ "accepted": 1 }
GET/api/v1/policy?agentId=…

The enforcement snapshot the SDK polls (freeze arrives here).

{ "agentId": "…", "status": "active", "revoked": false,
  "monthToDateCents": 184230, "policies": [ … ] }
POST/api/v1/reconcile

After-the-fact true-ups. Same idempotency as /event.

{ "events": [ { "agentId": "…", "amountCents": 12, … } ] }
{ "reconciled": 1 }

Dashboard endpoints (policies, approvals, freeze, billing) are authenticated with your session and documented in-app.

Environment

Everything is config-driven. Local dev needs nothing: with an empty DATABASE_URL the app runs on an in-process database and the seeded demo org. Each service below degrades gracefully when its keys are absent, so features light up as you add them.

DATABASE_URL
Postgres connection string. Leave empty for the in-process PGlite dev database.
NEXT_PUBLIC_APP_URL
Public base URL of the app. Used for redirects and links.
SPENDWALL_INGEST_URL
Base URL the SDK posts spend events to (defaults to /api/v1).
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
Clerk auth. Omit both keys to run in demo mode with the seeded org.
CLERK_SECRET_KEY
Clerk server key. Enables real sign-in and org sync.
CLERK_WEBHOOK_SECRET
Verifies Clerk org/user webhooks.
STRIPE_SECRET_KEY
Stripe billing. Without it, checkout returns a demo preview.
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
Stripe client key for checkout.
STRIPE_WEBHOOK_SECRET
Verifies Stripe subscription webhooks to sync plans.
STRIPE_PRICE_STARTER / _GROWTH / _SCALE
Stripe price IDs for each paid plan.
SLACK_WEBHOOK_URL
Posts alerts and approval requests to Slack.
SLACK_SIGNING_SECRET
Verifies Slack interactive approve/deny actions.