How to meter LLM token usage per customer for billing

If you resell model access inside your product, every customer's bill depends on how many tokens they used. This guide shows how to capture those numbers from the provider response, tie them to the right customer, and turn them into a monthly quantity you can invoice.

1. Read token counts from the provider, don't estimate them

Both major APIs return exact token counts with every response. Use those instead of counting tokens yourself:

ProviderField in the responseSource
OpenAI Chat Completionsusage.prompt_tokens, usage.completion_tokens, usage.total_tokensOpenAI API reference
Anthropic Messagesusage.input_tokens, usage.output_tokensAnthropic Messages API

With streaming, the usage arrives at the end of the stream (OpenAI needs stream_options: {"include_usage": true}; Anthropic reports it in the message start and delta events). Record usage once the stream finishes, not per chunk.

2. Decide what you bill: input, output or both

Providers price input and output tokens differently, and output usually costs more. You have two honest options:

3. Attribute every call to a customer

The provider knows your API key, not your customer. Attach your customer or subscription id at the point you make the call, typically from the authenticated request in your own backend. If you bill through Stripe, use the Stripe customer id so the monthly quantity maps straight onto an invoice.

4. Make the usage event retry-safe

If your usage report times out and you resend it, the customer must not be charged twice. Use a stable key per model call; the provider's response id (OpenAI id like chatcmpl-…, Anthropic id like msg_…) works well. More in deduping usage events with idempotency keys.

5. Send the event to Usage Lab

Create a sum meter once per token type:

curl -X POST https://usage-lab.marc-cf2.workers.dev/api/v1/meters \
  -H "Authorization: Bearer $USAGE_LAB_KEY" -H "Content-Type: application/json" \
  -d '{"key":"input_tokens","aggregation":"sum","name":"LLM input tokens"}'

curl -X POST https://usage-lab.marc-cf2.workers.dev/api/v1/meters \
  -H "Authorization: Bearer $USAGE_LAB_KEY" -H "Content-Type: application/json" \
  -d '{"key":"output_tokens","aggregation":"sum","name":"LLM output tokens"}'

After each model call, send both numbers in one batch. Events without their own key get <header>:0, <header>:1, so a retry of the whole batch is deduped:

curl -X POST https://usage-lab.marc-cf2.workers.dev/api/v1/usage \
  -H "Authorization: Bearer $USAGE_LAB_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: chatcmpl-abc123" \
  -d '[
    {"meter":"input_tokens","subscription":"cus_123","value":812,"properties":{"model":"gpt-4o-mini"}},
    {"meter":"output_tokens","subscription":"cus_123","value":240,"properties":{"model":"gpt-4o-mini"}}
  ]'

In Node, right after an OpenAI call:

const completion = await openai.chat.completions.create({ model, messages });
const { prompt_tokens, completion_tokens } = completion.usage;

await fetch("https://usage-lab.marc-cf2.workers.dev/api/v1/usage", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.USAGE_LAB_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": completion.id,        // stable across retries
  },
  body: JSON.stringify([
    { meter: "input_tokens",  subscription: customerId, value: prompt_tokens },
    { meter: "output_tokens", subscription: customerId, value: completion_tokens },
  ]),
});

With Anthropic, use message.id as the key and message.usage.input_tokens / output_tokens as the values. A resend with the same key and payload returns "status": "duplicate" and is not counted again; usage up to 30 days late lands in the month it happened.

6. Read each customer's monthly total

curl "https://usage-lab.marc-cf2.workers.dev/api/v1/meters/output_tokens/rollups?month=2026-10&subscription=cus_123" \
  -H "Authorization: Bearer $USAGE_LAB_KEY"

The response has total_quantity and a rollups array with quantity, event_count and pending_export_quantity per subscription.

7. Bill it

Stripe prices metered usage through usage-based billing, fed by meter events. Usage Lab's automatic push of monthly totals to Stripe Billing Meters is coming soon and runs in dry-run today, so for now read the rollup and report it to Stripe or your own biller.

Common mistakes

API docs · More examples · Pricing (CAD, flat)