← Blog

How to Build a Claude API Billing Dashboard

2026-10-29 · 5 min read · SubToAPI Team

If you're integrating Claude into a product and charging customers, or just trying to keep internal teams from blowing the budget, you need a billing dashboard that shows who used what, how much it cost, and whether anyone is close to a limit. Building this yourself means capturing token usage on every request, mapping it to a price per model, attributing it to a user or team, and rendering that data somewhere people can actually see it.

This article walks through the pieces you need — request logging, cost calculation, aggregation, and visualization — and shows where you can skip the plumbing entirely by using a platform that already tracks this for you.

What a Claude API billing dashboard actually needs to show

Before writing code, define the minimum viable dashboard. Most teams need:

Everything else (charts, export to CSV, Slack alerts) is a layer on top of these five data points.

Step 1: Capture usage data on every API call

Every Claude API response includes a usage object with input_tokens and output_tokens. You need to log this on every call, not just sample it, because billing has to be exact.

async function callClaude(prompt, userId) {
  const res = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "x-api-key": process.env.ANTHROPIC_API_KEY,
      "anthropic-version": "2023-06-01",
      "content-type": "application/json",
    },
    body: JSON.stringify({
      model: "claude-sonnet-4-5",
      max_tokens: 1024,
      messages: [{ role: "user", content: prompt }],
    }),
  });

  const data = await res.json();

  await db.usageLogs.insert({
    userId,
    model: data.model,
    inputTokens: data.usage.input_tokens,
    outputTokens: data.usage.output_tokens,
    timestamp: new Date(),
  });

  return data;
}

This is the foundation. If you skip this step, you'll have no way to reconstruct cost after the fact — Anthropic doesn't retroactively hand you a per-user usage report.

Step 2: Calculate cost per request

Each Claude model has a different price per million input and output tokens. Store a pricing table and compute cost at write time, not at query time, so historical costs stay accurate even if prices change later.

const PRICING = {
  "claude-opus-4": { input: 15, output: 75 },
  "claude-sonnet-4-5": { input: 3, output: 15 },
  "claude-haiku-4": { input: 0.8, output: 4 },
};

function calculateCost(model, inputTokens, outputTokens) {
  const rate = PRICING[model];
  const inputCost = (inputTokens / 1_000_000) * rate.input;
  const outputCost = (outputTokens / 1_000_000) * rate.output;
  return inputCost + outputCost;
}

Store the computed cost alongside the token counts in your usage log row. This avoids recalculating millions of rows every time you render a chart.

Step 3: Aggregate by user, key, or team

Once logs are flowing in, you need a query layer that rolls them up. A simple SQL aggregation gets you most of the way:

SELECT
  user_id,
  DATE_TRUNC('day', timestamp) AS day,
  SUM(input_tokens) AS total_input,
  SUM(output_tokens) AS total_output,
  SUM(cost) AS total_cost
FROM usage_logs
WHERE timestamp >= NOW() - INTERVAL '30 days'
GROUP BY user_id, day
ORDER BY day;

If you're attributing usage to teams, add a team_id column to the logs table and join it against your existing org/team tables. If you support per-key billing (common for B2B products reselling Claude access), log the API key ID instead of or alongside the user ID.

Step 4: Build the dashboard UI

The frontend is the easy part once the data model is right — a table of recent usage, a line chart of daily cost, and a per-user breakdown cover 90% of use cases. Any charting library (Recharts, Chart.js) works fine with the aggregated query results above.

The harder part is keeping this accurate under load: concurrent requests, retries, streaming responses where you only get final usage numbers after the stream closes, and handling partial failures where a request errors out after tokens were already consumed. Each of these is an edge case you have to handle explicitly in your logging code.

Skipping the plumbing with SubToAPI

If you're spending more time building usage-tracking infrastructure than your actual product, it's worth considering SubToAPI. It turns your Claude access into a clean HTTPS API with application API keys (sub_live_...), and usage metadata — tokens, cost, and per-key breakdowns — is already captured and exposed in the dashboard. You don't write a logging layer or a pricing table; each key's usage shows up automatically.

For teams, SubToAPI adds seats so you can see spend per team member without building your own user-to-cost attribution logic. A basic integration looks like a normal /v1/messages call:

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "Summarize this ticket"}]
  }'

Every call against that key is tracked, and you can see usage broken down in the dashboard instead of maintaining your own logs table. Plans start at €9 for Solo, €19/seat for Team, and €49/seat for Scale, with a free trial at signup. Full request/response details are in the docs and quickstart.

Build vs. buy

If you're billing external customers for Claude access at scale, building your own dashboard gives you full control over invoicing logic and data retention — worth the investment if usage data is core to your product. If you just need visibility into who's spending what across your team, wiring up Anthropic's raw API with your own logging adds weeks of work for something that already exists. Start with the lightest option that answers your actual question — "who's spending what, and are we near a limit" — and only build custom infrastructure once you've outgrown it.

FAQ

Does the Claude API return cost directly, or just token counts? Only token counts. The usage object gives you input_tokens and output_tokens; you calculate cost yourself using the published per-model pricing.

How do I track usage per team instead of per user? Add a team_id field to your usage logs at write time, then aggregate with GROUP BY team_id instead of user_id. If you don't want to build this, SubToAPI handles per-seat usage natively.

Can I set spending limits that block requests automatically? Yes, but you have to build it: check cumulative cost against a threshold before each request, or run a background job that disables a key once it crosses a limit.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →