Claude API Usage-Based Billing with Stripe: A Guide
If you're building a product on top of Claude and charging customers for it, flat subscriptions almost never match your actual costs. Token usage varies wildly between users — one customer sends short classification prompts, another runs long RAG pipelines with 50k-token contexts. The fix is usage-based billing: you track tokens consumed per customer and charge through Stripe's metered pricing, so your revenue scales with your Claude spend instead of guessing at a flat fee.
This article covers the practical mechanics: what usage data you need, how Stripe's metered billing API works, how to map Claude's token counts into Stripe usage records, and the architecture that keeps this reliable at scale. It also covers where things typically break — late usage reporting, double-counting, and reconciling streamed responses.
Why usage-based billing fits Claude API costs
Claude pricing is per-token, split between input and output tokens, with different rates per model. If you're reselling Claude access as part of a SaaS feature, your own costs move in lockstep with usage. Charging a flat $29/month works until a power user burns $400 of tokens that month — then you're losing money on your best customers.
Usage-based billing solves this by letting you:
- Pass through actual cost with a markup, rather than guessing at a flat tier
- Offer free trials that convert into metered billing without a plan change
- Give high-volume customers a lower effective rate while still capturing margin
- Avoid subsidizing outlier usage patterns
The tradeoff is complexity: you now need accurate, timely token metering, a billing pipeline that survives partial failures, and a way to reconcile Stripe invoices against actual API responses.
What you need to track
For every request you need, at minimum:
- Customer or account identifier
- Model used (pricing differs per model)
- Input tokens and output tokens (and cache read/write tokens if you use prompt caching)
- Timestamp
- Request/response ID for auditing and disputes
Claude's API returns usage data in the response body on every call — usage.input_tokens and usage.output_tokens. If you're calling Claude directly, you're responsible for capturing this on every single request, including streamed ones where the usage object only arrives in the final chunk.
If you're not calling Claude directly but routing through an API layer like SubToAPI, usage metadata is already exposed per key in the dashboard and in the response payload — see /docs/messages — which removes the need to build your own token-counting instrumentation from scratch.
Stripe's metered billing model
Stripe handles usage-based billing through metered prices attached to a subscription. The flow is:
- Create a Product and a recurring Price with
usage_type: metered. - Subscribe the customer to that price (no quantity needed upfront).
- Report usage throughout the billing period using usage records.
- Stripe aggregates usage records and bills automatically at period end.
Creating the metered price:
curl https://api.stripe.com/v1/prices \
-u $STRIPE_SECRET_KEY: \
-d "unit_amount=1" \
-d "currency=usd" \
-d "recurring[usage_type]=metered" \
-d "recurring[interval]=month" \
-d "billing_scheme=per_unit" \
-d "product=prod_XXXX"
Here unit_amount=1 means 1 cent per unit — you decide what a "unit" represents (e.g., 1,000 tokens, or a cost-weighted credit).
Reporting usage against a subscription item:
curl https://api.stripe.com/v1/subscription_items/si_XXXX/usage_records \
-u $STRIPE_SECRET_KEY: \
-d "quantity=1420" \
-d "timestamp=$(date +%s)" \
-d "action=increment"
action=increment adds to existing usage for the period; set overwrites it. For token billing, you almost always want increment.
Mapping Claude tokens to Stripe usage records
The core decision is: what is one "unit"? Two common approaches:
Approach 1 — report raw tokens, price per token. Simple but means your Stripe price has a tiny unit_amount (fractions of a cent), which Stripe supports but makes invoices harder to read.
Approach 2 — convert tokens to a cost-weighted credit, then report credits. You calculate (input_tokens input_rate + output_tokens output_rate) per request, round to a sensible unit (e.g., $0.001 per credit), and report credits. This is cleaner on invoices and lets you apply a markup in one place.
A minimal Node example that wraps a Claude call and reports usage:
async function callAndMeter(customerSubItemId, messages) {
const res = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": process.env.ANTHROPIC_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
body: JSON.stringify({ model: "claude-opus-4", max_tokens: 1024, messages }),
});
const data = await res.json();
const { input_tokens, output_tokens } = data.usage;
const credits = Math.ceil(input_tokens * 0.003 + output_tokens * 0.015);
await fetch(`https://api.stripe.com/v1/subscription_items/${customerSubItemId}/usage_records`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
"content-type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
quantity: String(credits),
timestamp: String(Math.floor(Date.now() / 1000)),
action: "increment",
}),
});
return data;
}
The same pattern applies whether you call Claude directly or through a proxy. If you use SubToAPI's keys (sub_live_...), you get the same usage object back in the response per /docs/quickstart, so this metering logic doesn't change — you just swap the endpoint and auth header.
A practical architecture
For anything beyond a side project, run metering as its own step, decoupled from the request path:
- App calls Claude (directly or via SubToAPI) and gets a response with usage data.
- App writes a usage event to a queue or database row immediately — don't rely on the Stripe call succeeding synchronously.
- A background worker batches events per customer per hour and reports them to Stripe with
increment. - A reconciliation job compares your internal usage log against Stripe's reported usage weekly to catch drift from retries or failed Stripe calls.
This decoupling matters because Stripe's usage record endpoint can fail or rate-limit, and you don't want a billing hiccup to break your product's response latency.
For streaming responses, the usage object only appears in the final SSE event (message_delta with a usage field), so your metering hook has to fire on stream completion, not on first token. See /docs/streaming for the event shapes if you're building this against SubToAPI.
Markup and pricing strategy
Most teams apply a fixed multiplier (e.g., 1.4–2x) on top of raw Claude cost per token, rather than exposing raw token pricing to customers. This absorbs volatility if Anthropic changes pricing and covers your infrastructure overhead. Whatever multiplier you choose, keep the conversion math in one function so you can adjust it without touching every call site.
If you'd rather not build and maintain the metering, key management, and dashboard yourself, that's effectively what SubToAPI does: it gives each application its own sub_live_... key, tracks usage per key, and surfaces it in a dashboard — see /pricing — so you can pull usage numbers straight into your own Stripe billing job instead of instrumenting every Claude call manually.
Common pitfalls
- Double-reporting on retries. If a request times out and you retry, make sure you don't meter it twice. Idempotency keys on your internal usage log help.
- Rounding too aggressively. Rounding every request up to the nearest credit adds up fast at high volume — round at the invoice level, not per-request, if precision matters.
- Ignoring cache tokens. If you use prompt caching, cached tokens are billed differently and your metering needs to account for that separately from fresh input tokens.
- Reporting usage too late. Stripe only aggregates usage records within the current billing period — if your worker lags past period close, that usage won't show on the right invoice.
questions
Can Stripe bill per-token directly without a credit conversion? Yes — set a very small unit_amount on the metered price and report raw token counts. It works but produces invoices with fractional-cent line items that are harder for customers to read.
Do I need a separate Stripe price for input vs output tokens? You can use two metered prices on the same subscription (one for input, one for output) if you want itemized billing, or combine them into one cost-weighted credit for simplicity. Most teams prefer the single-credit approach.
What happens if a usage record report to Stripe fails? The usage is lost unless you retry — Stripe doesn't pull usage from you. Always persist usage events in your own database first, then report to Stripe from a retryable background job.