Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

39 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Billing Tools, the get-paid engine for Stripe + WorkOS apps, for humans and AI agents

Drop-in auth + billing for any StripeΒ +Β WorkOS app, usable by humans and AI agents,
exposed as MCP tools, a REST API, and a CLI, over one storage-agnostic engine.

npm version npm downloads License: MIT TypeScript Stripe WorkOS MCP compatible Framework agnostic PRs welcome


Billing Tools packages the "get money in" plumbing you'd otherwise rebuild in every SaaS: API-key auth on top of WorkOS, token/credit + subscription billing on top of Stripe, and (because the next wave of customers is autonomous) first-class agent rails: auth.md self-registration and MPP machine payments. Wire it once; serve humans through a browser and agents through headless HTTP with the same engine. Storage is pluggable behind one small adapter (use the built-in WorkOS store, or mirror into your own Postgres).

Table of contents

Ship tools, not billing plumbing

The next generation of software is agents, and an agent is only as useful as the tools it can call. Tools are the product now.

But you don't go to war naked. Ship a tool into the wild with no auth and no way to get paid, and you're just donating GPU time to strangers. Someone still has to meter every call, hold a balance, bill the workspace, enforce seats, and cut off the freeloaders: the boring, identical, get-it-wrong-once-and-bleed-money layer that every AI product rebuilds from scratch.

Billing Tools is the armor. Bring a Stripe + WorkOS stack and you inherit, out of the box, the exact monetization model the frontier labs run on. The same shape as Anthropic, OpenAI, and xAI (Grok):

  • πŸ’° A price per token: every tool call costs tokens, metered and deducted automatically.
  • 🏒 Workspaces: the billable account, with one Stripe customer + balance each.
  • πŸ‘₯ Users per workspace: seats, invitations, and roles.
  • πŸͺ™ Tokens per user: per-seat grants, seat limits, and auto-reload.

So you do the one thing only you can do (build great tools) and monetize them the same afternoon. The get-paid layer is already handled, battle-tested, and agent-ready. πŸ›‘οΈ

Key Features

πŸ” Auth & identity

  • πŸ”‘ API-key auth: WorkOS organization API keys (Authorization: Bearer sk_…), validated on every request.
  • βœ‰οΈ Passwordless magic-auth: email a 6-digit code, get back a workspace/org + API key. No passwords.
  • πŸ€– auth.md agent self-registration: the WorkOS auth.md protocol (RFC 9728 Protected-Resource-Metadata + RFC 8414 Authorization-Server-Metadata + /agent/identity anonymous or verified-email + the claim ceremony), so agents onboard with no human signup.
  • ♻️ RFC 7009 revocation: revoke a key by value; always-200, no token-existence leak.
  • 🎫 Optional OAuth-JWT hook: bring your own MCP OAuth proxy via a single adapter method.

πŸ’³ Billing & payments

  • πŸͺ™ Usage-metered token wallet: held in the native Stripe customer credit balance (1 token = 1Β’), with idempotent credit/debit.
  • πŸ›’ Checkout top-ups: Stripe Checkout that auto-offers cards + AppleΒ Pay / GoogleΒ Pay / Link.
  • πŸ“¦ Declarative subscription plans: describe plans in code; products/prices are auto-provisioned in Stripe via lookup_key (immutable-price safe, orphan-cleaning). Zero dashboard clicks.
  • 🎟️ Per-seat / per-cycle token grants + seat limits: included tokens scale with active members.
  • πŸ” Auto-reload: off-session saved-card recharge when the balance drops below a threshold.
  • 🧾 Invoices + πŸ›οΈ Customer Portal: list invoices, and a one-call Stripe Billing Portal URL for self-serve upgrade/downgrade/cancel + card updates.
  • πŸ”’ Idempotency on every money move: welcome bonus, checkout credit, and per-cycle grants each carry a stable key, so retries/replays never double-charge.

πŸ€– Built for AI agents

  • πŸ“„ auth.md: the agent onboarding standard, served for you (/auth.md narrative + discovery metadata).
  • 🏧 MPP machine payments: Stripe's Machine Payments Protocol: a HTTP 402 + WWW-Authenticate: Payment challenge for pay-per-request (SPT card or crypto/USDC), the payment sibling of auth.md's 401.
  • 🧰 MCP server tools: get_api_key, get_token_balance, buy_tokens, list_plans, get_billing_portal, and more, drop straight into Claude, Cursor, or any MCP client.
  • 🧭 Discovery hints: every 401/402 advertises resource_metadata, so an agent can bootstrap unattended.

🧩 Three surfaces, one engine

  • πŸ› οΈ MCP: createMcpTransport({ register, adapter }).
  • 🌐 REST: createToolListHandler + createToolDispatchHandler (429/401/400/404 mapping built in).
  • ⌨️ CLI: registerBillingCommands(program, …) gives you auth, keys, balance, buy, invoices.
  • πŸͺ Stripe webhook: createStripeWebhookHandler() for instant checkout crediting.

πŸ”„ Zero-webhook sync

  • πŸ“‘ Event polling: reconcile Stripe and WorkOS via their Events APIs. No webhook endpoints, no signing secrets.
  • ⏱️ Any scheduler: run it in-process with sync.start() (persistent hosts) or as a serverless cron via createSyncRoute().
  • πŸͺž Generic DB mirror: shadow WorkOS orgs/users into your own tables (createMirror), driver-agnostic.
  • πŸ“‰ Dunning hook: invoice.payment_failed flips the org to past_due and fires an onPaymentFailed hook (Stripe Smart Retries do the retries).

🌍 Framework and platform agnostic

  • ⚑ Web-standard handlers: everything returns Request to Response; mount in Next.js, Hono, Bun, Deno, or Cloudflare Workers.
  • 🧱 No framework imports: runtime deps are just stripe + @workos-inc/node.
  • πŸ—„οΈ Bring your own DB: no pg dependency; you pass a query executor (any Postgres-compatible driver).
  • πŸ”Œ Pluggable storage adapter: WorkOS-only, or WorkOS + your DB mirror, behind one interface.
  • ☁️ No lock-in: runs on any Node host (Railway / Render / Fly / Vercel / self-host).

πŸ›οΈ Design doctrine

  • πŸ“ SDK-first: thin-wraps the Stripe & WorkOS SDKs (SDK types, pagination, typed errors, idempotency) so the lib evolves with the platforms instead of drifting.
  • 🎯 One memoized client per SDK: lazy, never constructed at import.
  • πŸ”’ Secure by default: AES-256-GCM session encryption at rest, SHA-256-hashed claim tokens, verified-domain-only internal-org checks.
  • πŸ§ͺ Offline-testable: the auth.md + MPP protocol surfaces are pure Request to Response, unit-testable without a live account.

Getting Started

Prerequisites

  • Node 18+
  • A Stripe secret key (STRIPE_SECRET_KEY)
  • A WorkOS API key + client id (WORKOS_API_KEY, WORKOS_CLIENT_ID)

Install

npm install @arnaudjnn/billing-tools
# peers (provide the ones you use): @modelcontextprotocol/sdk mcp-handler zod

Ships compiled dist/, so there's no build step or transpilePackages needed in the consumer.

Environment

STRIPE_SECRET_KEY=sk_test_…
STRIPE_WEBHOOK_SECRET=whsec_…        # only if you mount the webhook
WORKOS_API_KEY=sk_…
WORKOS_CLIENT_ID=client_…
INTERNAL_ORG_DOMAINS=acme.com        # optional: orgs with these verified domains are unmetered

Wire it up (one call)

createBilling() composes every surface from a single config (one module instance = one shared auth context). You supply the adapter + config; it returns the mounted handlers.

// billing.ts
import { createBilling } from "@arnaudjnn/billing-tools";
import { WorkOSOrgAdapter } from "@arnaudjnn/billing-tools/adapters/workos-org";

export const { mcp, restList, restDispatch, webhook, agentAuth } = createBilling({
  adapter: new WorkOSOrgAdapter(),                       // WorkOS is the source of truth
  config: { currency: "usd", baseUrl: process.env.APP_URL! },
  agentAuth: { branding: { productName: "Acme" } },     // enables auth.md (optional)
});
// app/[transport]/route.ts       β†’  export const { GET, POST } = mcp
// app/api/v0/route.ts            β†’  export const GET = restList
// app/api/v0/[tool]/route.ts     β†’  export const POST = restDispatch
// app/api/stripe/webhook/route.ts→  export const POST = webhook

Prefer fine-grained control? Every factory is exported individually (registerBillingTools, createMcpTransport, createToolListHandler, createToolDispatchHandler, createStripeWebhookHandler, createAgentAuth, …) so you can build your own composition root.

First call

# List available tools + costs
curl https://your-app.com/api/v0

# Use a key (Bearer sk_…)
curl -X POST https://your-app.com/api/v0/get_token_balance \
  -H "Authorization: Bearer sk_…"

Add agent onboarding with createAgentAuth and pay-per-call with createMachinePaymentHandler. See below.

How it works

WorkOS is always the source of truth. Your app's orgId is opaque: implement the adapter and everything (auth, metering, Stripe math, all surfaces) works unchanged. Two shipped patterns:

Pattern What orgId maps to Storage Use when
A: WorkOS-only the WorkOS org id none beyond WorkOS you don't have (or need) your own DB rows
B: WorkOS + DB mirror your own id (e.g. ws_…) a workos_org_id column + org.externalId, 1:1 you keep local rows and mirror WorkOS into them

In both, API keys are WorkOS org keys and the Stripe pointer lives on the org. The WorkOSOrgAdapter (@arnaudjnn/billing-tools/adapters/workos-org) implements both; pass a map for Pattern B.

Agent auth (auth.md)

Implements the WorkOS auth.md spec.

import { createAgentAuth } from "@arnaudjnn/billing-tools";

export const agentAuth = createAgentAuth({
  adapter, config,
  branding: { productName: "Acme", logoUri: "https://acme.com/logo.svg" },
  identityTypes: ["anonymous", "verified_email"],
});
// mount: /auth.md, /.well-known/oauth-{protected-resource,authorization-server},
//        /agent/identity, /agent/identity/claim, /oauth/token, /oauth/revoke

An agent hits a 401, follows the resource_metadata hint to your metadata, reads /auth.md, then registers (either anonymously for an instant key, or via a verified-email claim ceremony where the user reads back a code) and starts calling tools. All handlers are Request to Response.

Machine payments (MPP)

Implements Stripe's Machine Payments Protocol.

import { createMachinePaymentHandler } from "@arnaudjnn/billing-tools";

const pay = createMachinePaymentHandler({ methods: ["stripe"], amount: 50, currency: "usd" });
const gate = await pay.requirePayment(request);
if (gate instanceof Response) return gate;   // 402 + WWW-Authenticate: Payment challenge
// else settlement succeeded, serve the paid resource

The 402 challenge + credential parsing ship ready and are offline-testable. Actual settlement (card via shared payment tokens, or crypto/USDC) is injected via a settle function once your Stripe account is enabled for machine payments; until then the gate returns a clean "settlement not enabled" 402 (never a crash). createPaymentMd() serves an agent-facing /payment.md.

MCP OAuth proxy (dynamic clients)

MCP clients like Claude Desktop and Claude.ai can't be handed an API key β€” they register themselves (RFC 7591) and expect an OAuth 2.1 authorization-code flow. oauthProxy gives you that on top of WorkOS AuthKit, in one option:

const billing = createBilling({
  adapter, config,
  agentAuth: { branding: { productName: "Acme" } },
  oauthProxy: true,          // needs REFRESH_TOKEN_SECRET
});

// app/oauth/authorize/route.ts   export const GET  = billing.oauth!.authorize
// app/oauth/register/route.ts    export const POST = billing.oauth!.register
// app/oauth/callback/route.ts    export const GET  = billing.oauth!.callback
// app/oauth/token/route.ts       export const POST = billing.oauth!.token

The user authenticates with AuthKit; the client receives the WorkOS access token plus an HS256 refresh token that wraps the WorkOS one and is bound to the client_id that obtained it. PKCE (S256) is enforced whenever the client sends a challenge, authorization codes are single-use, and the /oauth/token route also serves the auth.md claim grant so one route covers both flows.

Enabling it also makes discovery honest: authorization_endpoint and registration_endpoint appear in /.well-known/oauth-authorization-server only when the proxy is on, and authorization_code/refresh_token join grant_types_supported. Consumers without the proxy no longer advertise an /oauth/authorize they don't implement.

REFRESH_TOKEN_SECRET is required and has no fallback. Falling back to WORKOS_CLIENT_ID β€” a public identifier β€” would let anyone who knows it forge a 30-day refresh token. Without the secret the token endpoint returns server_error rather than signing with something guessable.

CLI

import { registerBillingCommands } from "@arnaudjnn/billing-tools";
import { Command } from "commander";

const program = new Command();
registerBillingCommands(program, { configDir: "~/.acme", envPrefix: "ACME", defaultUrl: "https://acme.com" });
// acme auth | keys list|revoke | balance | buy | invoices

Configuration

BillingConfig (pass to resolveConfig):

Field Default Purpose
baseUrl required Checkout success/cancel + portal return URLs
currency "usd" Stripe currency
freeTokens 100 Welcome credit on first customer creation
internalDomains [] Orgs with these verified WorkOS domains are unmetered (see internalDomainsFromEnv)

Roadmap

  • 🧾 Stripe Tax (automatic_tax) opt-in
  • πŸͺ™ x402 machine-payment protocol alongside MPP

Contributing

Issues and PRs welcome. The engine is plain TypeScript compiled with tsc; dist/ is committed (consumers install via Git). See AGENTS.md for the architecture, the adapter interface, the SDK-first doctrine, and the release flow.

License

MIT Β© Arnaud Jeannin

About

The get-paid engine for Stripe + WorkOS apps, for humans and AI agents.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages