Stripe Usage-Based Billing with AI Gateway
Send AI Gateway token usage to an existing Stripe Billing Meters integration. When you include Stripe headers in your requests, AI Gateway automatically emits meter events for every successful response.
When you include Stripe headers in your requests, AI Gateway:
- Routes the request to the appropriate AI provider
- On a successful response, emits separate meter events for input, output, cache-read, and cache-write token counts when greater than zero
- Includes the customer ID, token count, token type (
input,output,cached_input, orcached_write), and model ID in each meter event
Stripe metering is non-blocking. If a meter event fails, AI Gateway still returns the AI response. Errors are logged for observability but don't affect the response.
Before you start, you'll need:
- A Stripe account with access to the Billing Meter API
- An existing billing meter with the event name
token-billing-tokensand dimension payload keysmodelandtoken_type, or access to Stripe's private preview for meter dimensions. If you have preview access, you can create the meter in one of two ways:- Use the token billing pricing plan flow, if Stripe has enabled it for your account. The flow creates pricing plans and the meter with the required configuration.
- Manually create a billing meter in your Stripe dashboard with the event name
token-billing-tokensand addmodelandtoken_typeas dimension payload keys.
- A Stripe restricted access key (
rk_...) with permission to write meter events - Stripe customer IDs (
cus_...) for the users you want to bill
If you don't have an existing configured meter or preview access, contact Stripe before following these setup steps. A standard Stripe account alone doesn't satisfy the prerequisites.
Stripe recommends Metronome for new usage-based billing integrations. AI Gateway's Stripe headers send Billing Meter events; they don't send events to Metronome's ingest API.
To bill with Metronome, collect token usage from AI responses in your application and send usage events through your Metronome integration. With AI SDK, see token usage. The Stripe header examples below apply only to the Billing Meters integration.
You configure Stripe billing entirely through HTTP headers. No changes to the request body are needed:
| Header | Required | Description |
|---|---|---|
stripe-customer-id | Yes | The Stripe customer ID to bill (e.g., cus_abc123) |
stripe-restricted-access-key | Yes | A Stripe restricted API key with meter event write permissions (e.g., rk_live_...) |
Both headers must be present for meter events to fire. If either is missing, the request proceeds normally without billing.
These examples use AI SDK 7 and the AI SDK for Python beta. Set AI_GATEWAY_API_KEY, STRIPE_CUSTOMER_ID, and STRIPE_RESTRICTED_ACCESS_KEY before running them. See API format differences for setup, request fields, and response handling.
import { generateText } from 'ai';
const { text } = await generateText({
model: 'anthropic/claude-sonnet-5',
prompt: 'Explain quantum computing in two sentences.',
headers: {
'stripe-customer-id': process.env.STRIPE_CUSTOMER_ID!,
'stripe-restricted-access-key': process.env.STRIPE_RESTRICTED_ACCESS_KEY!,
},
});
console.log(text);import asyncio
import os
import ai
async def main():
model = ai.get_model("anthropic/claude-sonnet-5")
messages = [ai.user_message("Explain quantum computing in two sentences.")]
params = ai.InferenceRequestParams(
extra_headers={"stripe-customer-id": os.environ["STRIPE_CUSTOMER_ID"], "stripe-restricted-access-key": os.environ["STRIPE_RESTRICTED_ACCESS_KEY"]}
)
async with ai.stream(model, messages, params=params) as stream:
async for event in stream:
if isinstance(event, ai.events.TextDelta):
print(event.chunk, end="", flush=True)
print()
asyncio.run(main())import OpenAI from 'openai';
const client = new OpenAI({
apiKey: process.env.AI_GATEWAY_API_KEY,
baseURL: 'https://ai-gateway.vercel.sh/v1',
defaultHeaders: {
'stripe-customer-id': process.env.STRIPE_CUSTOMER_ID!,
'stripe-restricted-access-key': process.env.STRIPE_RESTRICTED_ACCESS_KEY!,
},
});
const response = await client.chat.completions.create({
model: 'anthropic/claude-sonnet-5',
messages: [
{
role: 'user',
content: 'Explain quantum computing in two sentences.',
},
],
});
console.log(response.choices[0]?.message.content);import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["AI_GATEWAY_API_KEY"],
base_url="https://ai-gateway.vercel.sh/v1",
default_headers={"stripe-customer-id": os.environ["STRIPE_CUSTOMER_ID"], "stripe-restricted-access-key": os.environ["STRIPE_RESTRICTED_ACCESS_KEY"]},
)
response = client.chat.completions.create(
model="anthropic/claude-sonnet-5",
messages=[{"role": "user", "content": "Explain quantum computing in two sentences."}],
)
print(response.choices[0].message.content)curl --fail-with-body https://ai-gateway.vercel.sh/v1/chat/completions \
-H "Authorization: Bearer $AI_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-H "stripe-customer-id: $STRIPE_CUSTOMER_ID" \
-H "stripe-restricted-access-key: $STRIPE_RESTRICTED_ACCESS_KEY" \
-d '{
"model": "anthropic/claude-sonnet-5",
"messages": [
{
"role": "user",
"content": "Explain quantum computing in two sentences."
}
]
}'import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({
apiKey: process.env.AI_GATEWAY_API_KEY,
baseURL: 'https://ai-gateway.vercel.sh',
defaultHeaders: {
'stripe-customer-id': process.env.STRIPE_CUSTOMER_ID!,
'stripe-restricted-access-key': process.env.STRIPE_RESTRICTED_ACCESS_KEY!,
},
});
const response = await client.messages.create({
model: 'anthropic/claude-sonnet-5',
messages: [
{
role: 'user',
content: 'Explain quantum computing in two sentences.',
},
],
max_tokens: 1024,
});
for (const block of response.content) {
if (block.type === 'text') console.log(block.text);
}import os
from anthropic import Anthropic
client = Anthropic(
api_key=os.environ["AI_GATEWAY_API_KEY"],
base_url="https://ai-gateway.vercel.sh",
default_headers={"stripe-customer-id": os.environ["STRIPE_CUSTOMER_ID"], "stripe-restricted-access-key": os.environ["STRIPE_RESTRICTED_ACCESS_KEY"]},
)
response = client.messages.create(
model="anthropic/claude-sonnet-5",
messages=[{"role": "user", "content": "Explain quantum computing in two sentences."}],
max_tokens=1024,
)
for block in response.content:
if block.type == "text":
print(block.text)curl --fail-with-body https://ai-gateway.vercel.sh/v1/messages \
-H "Authorization: Bearer $AI_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-H "stripe-customer-id: $STRIPE_CUSTOMER_ID" \
-H "stripe-restricted-access-key: $STRIPE_RESTRICTED_ACCESS_KEY" \
-d '{
"model": "anthropic/claude-sonnet-5",
"messages": [
{
"role": "user",
"content": "Explain quantum computing in two sentences."
}
],
"max_tokens": 1024
}'import OpenAI from 'openai';
const client = new OpenAI({
apiKey: process.env.AI_GATEWAY_API_KEY,
baseURL: 'https://ai-gateway.vercel.sh/v1',
defaultHeaders: {
'stripe-customer-id': process.env.STRIPE_CUSTOMER_ID!,
'stripe-restricted-access-key': process.env.STRIPE_RESTRICTED_ACCESS_KEY!,
},
});
const response = await client.responses.create({
model: 'anthropic/claude-sonnet-5',
input: 'Explain quantum computing in two sentences.',
});
console.log(response.output_text);import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["AI_GATEWAY_API_KEY"],
base_url="https://ai-gateway.vercel.sh/v1",
default_headers={"stripe-customer-id": os.environ["STRIPE_CUSTOMER_ID"], "stripe-restricted-access-key": os.environ["STRIPE_RESTRICTED_ACCESS_KEY"]},
)
response = client.responses.create(
model="anthropic/claude-sonnet-5",
input="Explain quantum computing in two sentences.",
)
print(response.output_text)curl --fail-with-body https://ai-gateway.vercel.sh/v1/responses \
-H "Authorization: Bearer $AI_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-H "stripe-customer-id: $STRIPE_CUSTOMER_ID" \
-H "stripe-restricted-access-key: $STRIPE_RESTRICTED_ACCESS_KEY" \
-d '{
"model": "anthropic/claude-sonnet-5",
"input": "Explain quantum computing in two sentences."
}'Set the restricted key on a gateway instance, then pass the authenticated customer's Stripe ID on each request:
import { createGateway, generateText } from 'ai';
const gateway = createGateway({
headers: {
'stripe-restricted-access-key': process.env.STRIPE_RESTRICTED_ACCESS_KEY!,
},
});
export async function generateForCustomer(
stripeCustomerId: string,
prompt: string,
) {
const { text } = await generateText({
model: gateway('anthropic/claude-sonnet-5'),
prompt,
headers: {
'stripe-customer-id': stripeCustomerId,
},
});
return text;
}Look up stripeCustomerId from the authenticated user's billing record on your server. A shared customer ID would attribute every user's usage to the same customer. For HTTP clients, pass the customer header in the per-request options instead of setting it as a shared default.
For security, use a Stripe restricted API key instead of your secret key. The restricted key only needs permission to write billing meter events.
To create one:
- Go to Stripe Dashboard > Developers > API keys
- Click Create restricted key
- Enable Write permission for Billing meter events
- Save the key (starts with
rk_live_orrk_test_)
If the key is ever exposed, the blast radius is limited. It can't access customer data, create charges, or perform any other Stripe operations.
Each successful request can emit up to four events to Stripe's /v2/billing/meter_events endpoint. AI Gateway skips token types with a zero count. This example shows an input-token event:
{
"event_name": "token-billing-tokens",
"payload": {
"stripe_customer_id": "cus_abc123",
"value": "1500",
"token_type": "input",
"model": "anthropic/claude-sonnet-5"
}
}The model field identifies the serving provider and model. It uses provider/creator/model-name when the provider differs from the model creator, such as bedrock/anthropic/claude-sonnet-5. When they match, it uses creator/model-name, such as anthropic/claude-sonnet-5.
AI Gateway handles Stripe meter events with the following guarantees:
- Non-blocking: You always get the AI response, even if Stripe metering fails
- Idempotent: Each meter event has a unique identifier, which prevents duplicate billing
- Conditional: AI Gateway only emits events on successful responses and when token counts are greater than zero
- Observable: AI Gateway logs failed meter events for troubleshooting
Was this helpful?