Bill every model call to the right customer with 0 lost or double-counted events (daily reconciliation within 1% of provider usage), block requests within 1 s when prepaid credits reach zero, and generate invoices automatically at period end.
Recommended stack
- Metronome (a Stripe product) + @metronome/sdk 3 - Rate cards, LLM token pricing, credits, invoicing. Stripe now routes usage-based and LLM token billing through Metronome, with model prices for OpenAI, Anthropic, and Google synced automatically (public preview).
- Stripe (stripe-node 23) - Payments, Checkout for credit top-ups, customer portal. Metronome sends invoices to Stripe for collection.
- Postgres - Usage outbox ledger and balance cache. Guarantees no usage event is lost when a function freezes or the billing API is down.
- AI SDK 7 - Model calls and final usage numbers. onEnd (onFinish is a deprecated alias) reports normalized input, cache-read, cache-write and output tokens after the stream completes.
Build steps
- Pick the model: subscription with included credits plus overage, or prepaid credit packs. Define a custom pricing unit ("credits") and how each model converts to it.
- In Metronome, create a rate card with managed AI provider pricing and your markup (or custom rates), a package with the monthly credit allocation, and provision each customer with contracts/create linked to their Stripe customer.
- After each call finishes and the provider reports final usage, insert an outbox row: transaction_id = request id, customer_id, timestamp, event_type "token-billing", and properties model, provider, input_tokens, cached_input_tokens, output_tokens, cached_write_tokens, with each token counted in exactly one field.
- With AI SDK 7, map usage.inputTokenDetails.noCacheTokens to input_tokens, cacheReadTokens to cached_input_tokens, cacheWriteTokens to cached_write_tokens and outputTokens (includes reasoning) to output_tokens. If you read raw provider usage instead, follow Metronome's per-provider rules (OpenAI: subtract cache reads and writes from input; Gemini: add thinking tokens to output; Anthropic: map directly).
- A worker sends unsent rows to Metronome ingest in batches using the same transaction_id, retries on 5xx with backoff, and marks rows sent.
- Before each request, check the cached balance minus local unsent usage; when it is zero or below, return 402 with a top-up link.
- Handle top-ups with Stripe Checkout and a webhook that grants payment-gated credits.
- Run a daily job comparing ledger totals, Metronome usage, and provider usage reports; alert on drift above 1%.
Watch out for
- Sending usage before the stream ends misses output tokens. Send after final usage.
- Double-counting cached tokens: some providers include cache reads in the input total. Convert per provider.
- Fire-and-forget billing calls get lost on serverless freezes. Use the outbox.
- Checking balance only against the billing system lets users overspend during ingestion lag. Include local pending usage.
Definition of done
- Replaying the same 1,000 synthetic usage events twice shows exactly 1,000 events in Metronome.
- A customer at zero credits gets 402 within 1 s.
- The invoice preview matches the ledger total within 1%.
- A Stripe test-mode top-up increases the customer's credit balance.
- The daily reconciliation job runs on a test day and reports drift under 1%.