# Track Your LLM API Spend with a Python Token Usage Dashboard

> Source: <https://sourcefeed.dev/a/track-your-llm-api-spend-with-a-python-token-usage-dashboard>
> Published: 2026-08-09 17:41:12+00:00

# Track Your LLM API Spend with a Python Token Usage Dashboard

Log tokens and cost for every OpenAI, Anthropic, and Gemini call, then chart spend in Streamlit.

[Priya Nair](https://sourcefeed.dev/u/priya_nair)

## What you'll build

A small Python toolkit that logs tokens and dollar cost for every call you make to OpenAI, Anthropic, and Gemini into SQLite, plus a [Streamlit](https://streamlit.io) dashboard that shows total spend, cost per model, and cost per request — so you can spot which calls are burning your budget.

## Prerequisites

- Python 3.10+ (required by both
`openai`

and`google-genai`

) - API keys for
[OpenAI](https://platform.openai.com),[Anthropic](https://platform.claude.com), and[Gemini](https://ai.google.dev)— each provider needs its own account - Verified against:
`openai`

2.41.1,`anthropic`

0.121.0,`google-genai`

2.17.0,`streamlit`

1.61.0, and each provider's official pricing page as of August 2026 - Commands below are for macOS/Linux; on Windows PowerShell, replace
`export X="y"`

with`$env:X="y"`

## Step 1: Install the SDKs and set your keys

```
mkdir llm-spend && cd llm-spend
python -m venv .venv && source .venv/bin/activate
pip install openai anthropic google-genai streamlit pandas

export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."
export GEMINI_API_KEY="AIza..."
```

All three SDKs read their key from these exact environment variable names automatically — no key-passing code needed.

## Step 2: Write the usage tracker

Create `tracker.py`

. The price table is USD per 1M tokens, taken from each provider's official pricing page (August 2026) — prices change, so re-check them when you add models.

``` python
import sqlite3
import time

DB = "usage.db"

# USD per 1M tokens — verify against official pricing pages before trusting the totals
PRICES = {
    "gpt-5.6-terra":    {"input": 2.00, "output": 12.00},
    "claude-opus-5":    {"input": 5.00, "output": 25.00},
    "gemini-3.6-flash": {"input": 1.50, "output": 7.50},
}

def init_db():
    with sqlite3.connect(DB) as con:
        con.execute("""CREATE TABLE IF NOT EXISTS usage (
            ts REAL, provider TEXT, model TEXT,
            input_tokens INTEGER, output_tokens INTEGER, cost_usd REAL)""")

def log_usage(provider, model, input_tokens, output_tokens):
    p = PRICES[model]
    cost = (input_tokens * p["input"] + output_tokens * p["output"]) / 1_000_000
    with sqlite3.connect(DB) as con:
        con.execute("INSERT INTO usage VALUES (?, ?, ?, ?, ?, ?)",
                    (time.time(), provider, model, input_tokens, output_tokens, cost))
    return cost
```

## Step 3: Instrument each provider

Create `llm.py`

. Each wrapper makes the call, reads the provider's usage object, and logs it. The field names differ per provider — that's the whole reason to centralize this.

``` python
import anthropic
from google import genai
from openai import OpenAI
from tracker import init_db, log_usage

init_db()
openai_client = OpenAI()
anthropic_client = anthropic.Anthropic()
gemini_client = genai.Client()

def ask_openai(prompt: str) -> str:
    r = openai_client.responses.create(model="gpt-5.6-terra", input=prompt)
    log_usage("openai", "gpt-5.6-terra", r.usage.input_tokens, r.usage.output_tokens)
    return r.output_text

def ask_anthropic(prompt: str) -> str:
    r = anthropic_client.messages.create(
        model="claude-opus-5",
        max_tokens=8192,  # caps thinking + answer together; don't lowball it
        messages=[{"role": "user", "content": prompt}],
    )
    log_usage("anthropic", "claude-opus-5", r.usage.input_tokens, r.usage.output_tokens)
    # thinking blocks can precede the text block, so filter by type
    return next(b.text for b in r.content if b.type == "text")

def ask_gemini(prompt: str) -> str:
    r = gemini_client.interactions.create(model="gemini-3.6-flash", input=prompt)
    # Gemini reports thinking tokens separately but bills them at the output rate
    out = r.usage.total_output_tokens + (r.usage.total_thought_tokens or 0)
    log_usage("google", "gemini-3.6-flash", r.usage.total_input_tokens, out)
    return r.output_text
```

Claude's `usage.output_tokens`

already includes thinking tokens; Gemini's Interactions API splits them into `total_thought_tokens`

, so we add them back before pricing.

## Step 4: Generate some traffic

Create `demo.py`

and run it with `python demo.py`

:

``` python
from llm import ask_anthropic, ask_gemini, ask_openai

q = "In one sentence, why is the sky blue?"
for name, fn in [("OpenAI", ask_openai), ("Anthropic", ask_anthropic), ("Gemini", ask_gemini)]:
    print(f"{name}: {fn(q)[:100]}")
```

## Step 5: Build the dashboard

Create `dashboard.py`

:

``` python
import sqlite3
import pandas as pd
import streamlit as st

st.title("LLM API Spend")

with sqlite3.connect("usage.db") as con:
    df = pd.read_sql_query("SELECT * FROM usage", con)
df["ts"] = pd.to_datetime(df["ts"], unit="s")

c1, c2, c3 = st.columns(3)
c1.metric("Total spend", f"${df.cost_usd.sum():.4f}")
c2.metric("Requests", len(df))
c3.metric("Avg cost / request", f"${df.cost_usd.mean():.4f}")

st.subheader("Spend by model")
st.bar_chart(df.groupby("model")["cost_usd"].sum())

st.subheader("Cost per request over time")
st.scatter_chart(df, x="ts", y="cost_usd", color="model")

st.subheader("Raw log")
st.dataframe(df.sort_values("ts", ascending=False))
```

Launch it:

```
streamlit run dashboard.py
```

## Verify it works

`python demo.py`

should print one answer per provider, then confirm the rows landed:

``` python
python -c "import sqlite3; [print(r) for r in sqlite3.connect('usage.db').execute(
    'SELECT provider, model, input_tokens, output_tokens, round(cost_usd, 6) FROM usage')]"
```

Expected (token counts will vary):

```
('openai', 'gpt-5.6-terra', 18, 24, 0.000324)
('anthropic', 'claude-opus-5', 16, 310, 0.00783)
('google', 'gemini-3.6-flash', 11, 245, 0.001854)
```

`streamlit run dashboard.py`

prints `Local URL: http://localhost:8501`

and opens a page with three metric tiles, a bar chart of spend per model, and a scatter of cost per request. The Anthropic bar will dominate — Claude Opus 5's $25/1M output rate plus thinking tokens is exactly the kind of thing this dashboard exists to surface.

## Troubleshooting

— the key isn't in this shell.`openai.AuthenticationError: Error code: 401 ... Incorrect API key provided`

`export OPENAI_API_KEY=...`

in the same terminal you run Python from (env vars don't persist across sessions), then retry.— same cause on the Anthropic side:`anthropic.AuthenticationError: Error code: 401 ... {'type': 'authentication_error', 'message': 'invalid x-api-key'}`

`ANTHROPIC_API_KEY`

is unset or pasted with whitespace. Re-export it.— Gemini keys come from`google.genai.errors.ClientError: 400 INVALID_ARGUMENT ... API key not valid. Please pass a valid API key.`

[Google AI Studio](https://aistudio.google.com), not Google Cloud Console; make sure`GEMINI_API_KEY`

holds an AI Studio key.when opening the dashboard — you ran Streamlit before any calls were logged. Run`sqlite3.OperationalError: no such table: usage`

`python demo.py`

first so`usage.db`

exists.

## Next steps

- Log cached-token counts (
`cache_read_input_tokens`

on Anthropic,`total_cached_tokens`

on Gemini) — cached input is billed at a steep discount, and separating it shows whether prompt caching is actually working. - Set a daily budget: query today's
`SUM(cost_usd)`

before each call and raise if you're over. - For production, ship the rows to Postgres instead of SQLite and tag each one with a feature or user ID so spend maps to product areas.

## Sources & further reading

-
[API Pricing](https://developers.openai.com/api/docs/pricing)— developers.openai.com -
[Developer quickstart](https://developers.openai.com/api/docs/quickstart)— developers.openai.com -
[Gemini API Pricing](https://ai.google.dev/gemini-api/docs/pricing)— ai.google.dev -
[Interactions API reference](https://ai.google.dev/api/interactions-api)— ai.google.dev -
[Pricing](https://platform.claude.com/docs/en/pricing)— platform.claude.com -
[2026 release notes](https://docs.streamlit.io/develop/quick-reference/release-notes/2026)— docs.streamlit.io

[Priya Nair](https://sourcefeed.dev/u/priya_nair)· AI & Developer Experience Writer

Priya covers AI frameworks, developer productivity tooling, and the startup ecosystem across South and Southeast Asia, bringing a researcher's rigour and a practitioner's empathy to every story. She is deeply sceptical of benchmarks and asks hard questions so her readers don't have to.

## Discussion 0

No comments yet

Be the first to weigh in.
