DEV Community

John Medina
John Medina

Posted on

How to Track Per-User OpenAI Costs in Your Next.js App

So you've integrated the OpenAI API into your Next.js app. Your users love it. But then you get the bill, and you have no idea who's costing you what. A single "power user" could be driving 90% of your costs, and you'd be flying blind.

Let's fix that. Tracking per-user costs isn't just a nice-to-have, it's essential for any application that exposes an LLM to end-users.

The Problem: The Un-instrumented API Call

Your code might look something like this. You have an API route in Next.js that calls OpenAI.

// pages/api/generate.ts
import OpenAI from 'openai';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export default async function handler(req, res) {
  const completion = await openai.chat.completions.create({
    messages: [{ role: 'user', content: 'Say this is a test' }],
    model: 'gpt-4o',
  });

  res.status(200).json({ result: completion.choices[0].message.content });
}
Enter fullscreen mode Exit fullscreen mode

This works, but it's a black box. You know a call was made, but you don't know who made it.

The Solution: Attribute Every Call

To fix this, you need to attribute every single API call to a user. This means passing a unique user identifier with every request.

First, protect your API route to make sure you have a user session. Then, wrap your OpenAI calls in a helper function that logs the cost.

// lib/openai.ts
import OpenAI from 'openai';
import { getCostForModel, saveUsageToDatabase } from './billing'; // Fictional helpers

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export async function createChatCompletion(userId: string, options: any) {
  const completion = await openai.chat.completions.create(options);

  const promptTokens = completion.usage?.prompt_tokens ?? 0;
  const completionTokens = completion.usage?.completion_tokens ?? 0;

  // Calculate cost based on model (you have to maintain these prices)
  const cost = getCostForModel(options.model, promptTokens, completionTokens);

  // Save to your database
  await saveUsageToDatabase({
    userId,
    model: options.model,
    promptTokens,
    completionTokens,
    cost,
  });

  return completion;
}
Enter fullscreen mode Exit fullscreen mode

You'd need a database table to store this, maybe in Supabase:

CREATE TABLE llm_usage (
  id bigserial primary key,
  created_at timestamp with time zone default now(),
  user_id text not null,
  model text not null,
  prompt_tokens integer not null,
  completion_tokens integer not null,
  cost numeric(10, 6) not null
);
Enter fullscreen mode Exit fullscreen mode

The Shortcut: Don't Build It Yourself

As you can see, this is a lot of boilerplate. You need to maintain pricing for different models, build a robust logging system, and create a dashboard to view the data.

This is a problem I ran into, so I built LLMeter (llmeter.org). It's an open-source, AGPL-licensed dashboard you can self-host. It gives you a simple SDK to wrap your LLM calls and automatically handles the cost calculation, logging, and dashboarding for multiple providers (OpenAI, Anthropic, Mistral, etc.).

It turns the code above into something much simpler and gives you a production-ready dashboard out of the box.

Whether you build it yourself or use a tool, the takeaway is the same: if you're not tracking LLM costs on a per-user basis, you're driving with your eyes closed. Don't wait for the surprise bill to learn that lesson.

Top comments (0)