osquarski OpenAI example **Since OpenAI does not include cost, I use a custom pricing map to return it** [details="Codeblock"] ```typescript import { UIMessage, convertToModelMessages, generateText, wrapLanguageModel, } from 'ai'; import { createOpenAI } from '@ai-sdk/openai'; import { createOpenAIMiddleware } from '@ai-billing/openai'; import { consoleDestination, createObjectPriceResolver, ModelPricing, } from '@ai-billing/core'; const openai = createOpenAI({ // eslint-disable-next-line turbo/no-undeclared-env-vars apiKey: process.env.OPENAI_API_KEY, }); const customPricingMap: Record<string, ModelPricing> = { 'gpt-5': { promptTokens: 1.25 / 1_000_000, completionTokens: 10.0 / 1_000_000, inputCacheReadTokens: 0.125 / 1_000_000, }, 'gpt-4o': { promptTokens: 5.0 / 1_000_000, completionTokens: 15.0 / 1_000_000, }, }; const priceResolver = createObjectPriceResolver(customPricingMap); const billingMiddleware = createOpenAIMiddleware({ destinations: [consoleDestination()], priceResolver: priceResolver, }); export async function POST() { try { const messages: UIMessage[] = [ { id: 'test-gen-1', role: 'user', parts: [{ type: 'text', text: 'What is the capital of Sweden?' }], }, ]; const model = 'gpt-5'; const wrappedModel = wrapLanguageModel({ model: openai(model), middleware: billingMiddleware, }); const result = await generateText({ model: wrappedModel, messages: await convertToModelMessages(messages), }); return Response.json(result); } catch (error) { console.error('Generate Error:', error); return Response.json({ error: (error as Error).message }, { status: 500 }); } } ``` [/details]
osquarski @ai-billing also sends usage events to Billing Providers Here is an infographic how: And a codesnippet showing how to do it for: Stripe [details="Codesnippet"] ```typescript import { createOpenRouter } from '@openrouter/ai-sdk-provider'; import { streamText, convertToModelMessages, UIMessage, wrapLanguageModel, } from 'ai'; import { createOpenRouterV3Middleware } from '@ai-billing/openrouter'; import { consoleDestination } from '@ai-billing/core'; import { createStripeDestination } from '@ai-billing/stripe'; type BillingTags = { org_name?: string; stripe_customer_id?: string; }; const stripeDestination = createStripeDestination<BillingTags>({ apiKey: `${process.env.STRIPE_SECRET_KEY}`, // Make sure this is in your .env meterName: 'llm_usage', // The slug from your Stripe dashboard }); const openrouter = createOpenRouter({ // eslint-disable-next-line turbo/no-undeclared-env-vars apiKey: process.env.OPENROUTER_API_KEY, }); //const consoleLogger = new ConsoleDestination(); const billingMiddleware = createOpenRouterV3Middleware<BillingTags>({ destinations: [consoleDestination(), stripeDestination], }); export async function POST() { const messages: UIMessage[] = [ { id: 'test-message-123', role: 'user', parts: [ { type: 'text', text: 'What is the capital of Sweden?', }, ], }, ]; const model = 'google/gemini-2.0-flash-001'; const wrappedModel = wrapLanguageModel({ model: openrouter(model), middleware: billingMiddleware, }); const result = streamText({ model: wrappedModel, messages: await convertToModelMessages(messages), providerOptions: { 'ai-billing-tags': { stripe_customer_id: 'cus_UIMLD4AuBpC8Ux', // This has to be defined as customer_mapping in your Stripe meter org_name: 'acme corp', // This will end up in Stripe metadata } as BillingTags, }, }); return result.toUIMessageStreamResponse(); } ``` [/details] Polar [details="Codesnippet"] ```typescript import { createOpenRouter } from '@openrouter/ai-sdk-provider'; import { streamText, convertToModelMessages, UIMessage, wrapLanguageModel, } from 'ai'; import { createOpenRouterV3Middleware } from '@ai-billing/openrouter'; import { consoleDestination } from '@ai-billing/core'; import { createPolarDestination } from '@ai-billing/polar'; type BillingTags = { customer_id?: string; org_name?: string; }; const polarDestination = createPolarDestination<BillingTags>({ accessToken: process.env.POLAR_ACCESS_TOKEN, // Make sure this is in your .env eventName: 'llm_usage', // The slug from your Polar dashboard server: 'sandbox', // Good for testing! }); const openrouter = createOpenRouter({ // eslint-disable-next-line turbo/no-undeclared-env-vars apiKey: process.env.OPENROUTER_API_KEY, }); //const consoleLogger = new ConsoleDestination(); const billingMiddleware = createOpenRouterV3Middleware<BillingTags>({ destinations: [consoleDestination(), polarDestination], }); export async function POST() { const messages: UIMessage[] = [ { id: 'test-message-123', role: 'user', parts: [ { type: 'text', text: 'What is the capital of Sweden?', }, ], }, ]; const model = 'google/gemini-2.0-flash-001'; const wrappedModel = wrapLanguageModel({ model: openrouter(model), middleware: billingMiddleware, }); const result = streamText({ model: wrappedModel, messages: await convertToModelMessages(messages), providerOptions: { 'ai-billing-tags': { customer_id: '4a874ea3-53ec-432d-9d55-c55bf957e18f', // This triggers internalCustomerId in Polar org_name: 'Acme Corp', // This will end up in Polar metadata } as BillingTags, }, }); return result.toUIMessageStreamResponse(); } ``` [/details] Lago [details="Codesnippet"] ```typescript import { createOpenRouter } from '@openrouter/ai-sdk-provider'; import { streamText, convertToModelMessages, UIMessage, wrapLanguageModel, } from 'ai'; import { createOpenRouterV3Middleware } from '@ai-billing/openrouter'; import { consoleDestination } from '@ai-billing/core'; import { createLagoDestination } from '@ai-billing/lago'; type BillingTags = { userId?: string; }; const lagoDestination = createLagoDestination<BillingTags>({ apiKey: `${process.env.LAGO_API_KEY}`, apiUrl: process.env.LAGO_API_URL, meterCode: 'llm_usage', }); const openrouter = createOpenRouter({ // eslint-disable-next-line turbo/no-undeclared-env-vars apiKey: process.env.OPENROUTER_API_KEY, }); const billingMiddleware = createOpenRouterV3Middleware<BillingTags>({ destinations: [consoleDestination(), lagoDestination], }); export async function POST() { const messages: UIMessage[] = [ { id: 'test-message-123', role: 'user', parts: [ { type: 'text', text: 'What is the capital of Sweden?', }, ], }, ]; const model = 'openai/gpt-4o'; const wrappedModel = wrapLanguageModel({ model: openrouter(model), middleware: billingMiddleware, }); const result = streamText({ model: wrappedModel, messages: await convertToModelMessages(messages), providerOptions: { 'ai-billing-tags': { userId: 'user_lago_test', } as BillingTags, }, }); return result.toUIMessageStreamResponse(); } ``` [/details] OpenMeter [details="Codesnippet"] ```typescript import { createOpenRouter } from '@openrouter/ai-sdk-provider'; import { streamText, convertToModelMessages, UIMessage, wrapLanguageModel, } from 'ai'; import { createOpenRouterV3Middleware } from '@ai-billing/openrouter'; import { consoleDestination } from '@ai-billing/core'; import { createOpenMeterDestination } from '@ai-billing/openmeter'; type BillingTags = { userId?: string; org_name?: string; }; const openMeterDestination = createOpenMeterDestination<BillingTags>({ apiKey: `${process.env.OPENMETER_API_KEY}`, }); const openrouter = createOpenRouter({ // eslint-disable-next-line turbo/no-undeclared-env-vars apiKey: process.env.OPENROUTER_API_KEY, }); const billingMiddleware = createOpenRouterV3Middleware<BillingTags>({ destinations: [consoleDestination(), openMeterDestination], }); export async function POST() { const messages: UIMessage[] = [ { id: 'test-message-123', role: 'user', parts: [{ type: 'text', text: 'What is the capital of Sweden?' }], }, ]; const model = 'openai/gpt-4o'; const wrappedModel = wrapLanguageModel({ model: openrouter(model), middleware: billingMiddleware, }); const result = streamText({ model: wrappedModel, messages: await convertToModelMessages(messages), providerOptions: { 'ai-billing-tags': { userId: 'user_openmeter_test', org_name: 'Acme Corp', } as BillingTags, }, }); return result.toUIMessageStreamResponse(); } ``` [/details]
osquarski Installation ```bash npm install @ai-billing/core @ai-billing/openrouter # Example for OpenRouter ``` Full-stack @ai-billing examples | Name | Demo Link | Repo | Deploy | | :--- | :--- | :--- | :--- | | **Chatbot (OpenRouter + Polar)** | View Demo | GitHub | | | **Chatbot (OpenAI + Polar)** | View Demo | GitHub | | | **Chatbot (Stripe)** | View Demo | GitHub | |
Cjsgkanwjr15 escalated to case #01WSl5Xa5wesboTj via the support form, with full traffic analysis attached. IXXSPMPV-0003 reaches the 60-day refund window on Aug 3 — requesting a staff member route this to billing before then. Traffic analysis attached to the case: /breweries received 82.8k requests and /products 48.4k, while the homepage received only 90. /robots.txt was fetched 20 times. Source IPs cluster in a single datacenter /24.
Amy Egan It looks like you already received partial refunds for the invoices listed. The team will need to review your specific case to determine if any additional refund is appropriate based on the specific details of your case. If you have not already, you should implement additional protections to prevent further high usage costs. Rate limits, bot management, WAF custom rules, or simply pausing your project are relatively straightforward options to limit or prevent further usage costs. I would also recommend spend management to limit excessive usage leading to higher bills. Thanks for your patience while the team works to resolve all refund inquiries as quickly as possible
Cjsgkanwjr15 Thank you, Amy. I appreciate the clarification. Yes, I understand that the partial refunds have already been applied, but those credits were only for the Pro plan proration and do not cover the bot-driven usage charges. Since you mentioned that the team needs to review my specific case to determine whether an additional refund is appropriate, could you please make sure that **Case #01C19RYpO6OicLKx, which I originally opened on July 14, is routed to the billing team for review?** I would also appreciate it if the team could consider **July 14, the date I first submitted the refund inquiry**, when evaluating the applicable refund window. I raised the issue promptly after the abnormal usage occurred, and the delay in receiving a response was outside my control. I have already taken the recommended preventative measures by downgrading to the Hobby plan and implementing bot blocking. The main request remains an additional refund for the **bot-driven usage charges on invoices IXXSPMPV-0003, IXXSPMPV-0004, and IXXSPMPV-0005**, excluding the Pro subscription fees and the partial credits already issued. I can provide the Top User Agents screenshots and the detailed traffic analysis again if the billing team needs them. Thank you for helping route this to the appropriate team.
sprucepad The title seems to have been changed, it shows “25m / 0s” on the dashboard. It also seems to be completely ignored, as I just deployed something just fine.
sprucepad It seems to have disappeared, so maybe Vercel’s adding build time limits and it was glitched out.
Aconfusedprogrammer I had this issue too. I still have it, but I can still deploy just fine.
Pauline P. Narvas In most cases, if you exceed your usage limits on the Hobby plan, you will have to wait 30 days before you can use the feature again.
mwask will all my projects be paused???
Longnguyenthanh075 5963 I am having the same issue as I approaching end limits of 10GB, the document said up to 10GB for Hobby plan and no mention of resetting as well. I am well past that 30-day period but have not see it resets. I checked with the CS bots from vercel it said the limit will be resetted.
system Thank you for bringing this to our attention. To ensure this is investigated with the necessary priority and privacy, please report all security-related concerns, potential exploits, or abuse directly to the Vercel Security Team. Please submit your report here: vercel.com/abuse Reporting via this official channel is the fastest way to reach our security engineers and ensures that sensitive information is handled in a secure environment rather than a public forum.
Kawaki Nagisa I got a response that my 2 invoices will be refunded but I only got 1 refund invoice the other is still in paid status while the whole 1500 creds were removed so can someone from support help me expedite this bcs the support ticket got auto closed after there response I did create a follow up case : 0147h2jnlrddNrGY original case id : 01d14EiL4Dls9pih