{"slug": "track-your-llm-api-spend-with-a-python-token-usage-dashboard", "title": "Track Your LLM API Spend with a Python Token Usage Dashboard", "summary": "Priya Nair published a tutorial for building a Python token usage dashboard that logs tokens and dollar cost for every OpenAI, Anthropic, and Gemini API call into SQLite and visualizes spend via a Streamlit app. The toolkit, verified against openai 2.41.1, anthropic 0.121.0, google-genai 2.17.0, and streamlit 1.61.0 as of August 2026, uses per-model price tables (e.g., gpt-5.6-terra at $2.00 input/$12.00 output per 1M tokens) and provider-specific wrappers to capture usage data. The dashboard helps developers identify which calls are driving up their LLM API costs.", "body_md": "# Track Your LLM API Spend with a Python Token Usage Dashboard\n\nLog tokens and cost for every OpenAI, Anthropic, and Gemini call, then chart spend in Streamlit.\n\n[Priya Nair](https://sourcefeed.dev/u/priya_nair)\n\n## What you'll build\n\nA 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.\n\n## Prerequisites\n\n- Python 3.10+ (required by both\n`openai`\n\nand`google-genai`\n\n) - API keys for\n[OpenAI](https://platform.openai.com),[Anthropic](https://platform.claude.com), and[Gemini](https://ai.google.dev)— each provider needs its own account - Verified against:\n`openai`\n\n2.41.1,`anthropic`\n\n0.121.0,`google-genai`\n\n2.17.0,`streamlit`\n\n1.61.0, and each provider's official pricing page as of August 2026 - Commands below are for macOS/Linux; on Windows PowerShell, replace\n`export X=\"y\"`\n\nwith`$env:X=\"y\"`\n\n## Step 1: Install the SDKs and set your keys\n\n```\nmkdir llm-spend && cd llm-spend\npython -m venv .venv && source .venv/bin/activate\npip install openai anthropic google-genai streamlit pandas\n\nexport OPENAI_API_KEY=\"sk-...\"\nexport ANTHROPIC_API_KEY=\"sk-ant-...\"\nexport GEMINI_API_KEY=\"AIza...\"\n```\n\nAll three SDKs read their key from these exact environment variable names automatically — no key-passing code needed.\n\n## Step 2: Write the usage tracker\n\nCreate `tracker.py`\n\n. 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.\n\n``` python\nimport sqlite3\nimport time\n\nDB = \"usage.db\"\n\n# USD per 1M tokens — verify against official pricing pages before trusting the totals\nPRICES = {\n    \"gpt-5.6-terra\":    {\"input\": 2.00, \"output\": 12.00},\n    \"claude-opus-5\":    {\"input\": 5.00, \"output\": 25.00},\n    \"gemini-3.6-flash\": {\"input\": 1.50, \"output\": 7.50},\n}\n\ndef init_db():\n    with sqlite3.connect(DB) as con:\n        con.execute(\"\"\"CREATE TABLE IF NOT EXISTS usage (\n            ts REAL, provider TEXT, model TEXT,\n            input_tokens INTEGER, output_tokens INTEGER, cost_usd REAL)\"\"\")\n\ndef log_usage(provider, model, input_tokens, output_tokens):\n    p = PRICES[model]\n    cost = (input_tokens * p[\"input\"] + output_tokens * p[\"output\"]) / 1_000_000\n    with sqlite3.connect(DB) as con:\n        con.execute(\"INSERT INTO usage VALUES (?, ?, ?, ?, ?, ?)\",\n                    (time.time(), provider, model, input_tokens, output_tokens, cost))\n    return cost\n```\n\n## Step 3: Instrument each provider\n\nCreate `llm.py`\n\n. 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.\n\n``` python\nimport anthropic\nfrom google import genai\nfrom openai import OpenAI\nfrom tracker import init_db, log_usage\n\ninit_db()\nopenai_client = OpenAI()\nanthropic_client = anthropic.Anthropic()\ngemini_client = genai.Client()\n\ndef ask_openai(prompt: str) -> str:\n    r = openai_client.responses.create(model=\"gpt-5.6-terra\", input=prompt)\n    log_usage(\"openai\", \"gpt-5.6-terra\", r.usage.input_tokens, r.usage.output_tokens)\n    return r.output_text\n\ndef ask_anthropic(prompt: str) -> str:\n    r = anthropic_client.messages.create(\n        model=\"claude-opus-5\",\n        max_tokens=8192,  # caps thinking + answer together; don't lowball it\n        messages=[{\"role\": \"user\", \"content\": prompt}],\n    )\n    log_usage(\"anthropic\", \"claude-opus-5\", r.usage.input_tokens, r.usage.output_tokens)\n    # thinking blocks can precede the text block, so filter by type\n    return next(b.text for b in r.content if b.type == \"text\")\n\ndef ask_gemini(prompt: str) -> str:\n    r = gemini_client.interactions.create(model=\"gemini-3.6-flash\", input=prompt)\n    # Gemini reports thinking tokens separately but bills them at the output rate\n    out = r.usage.total_output_tokens + (r.usage.total_thought_tokens or 0)\n    log_usage(\"google\", \"gemini-3.6-flash\", r.usage.total_input_tokens, out)\n    return r.output_text\n```\n\nClaude's `usage.output_tokens`\n\nalready includes thinking tokens; Gemini's Interactions API splits them into `total_thought_tokens`\n\n, so we add them back before pricing.\n\n## Step 4: Generate some traffic\n\nCreate `demo.py`\n\nand run it with `python demo.py`\n\n:\n\n``` python\nfrom llm import ask_anthropic, ask_gemini, ask_openai\n\nq = \"In one sentence, why is the sky blue?\"\nfor name, fn in [(\"OpenAI\", ask_openai), (\"Anthropic\", ask_anthropic), (\"Gemini\", ask_gemini)]:\n    print(f\"{name}: {fn(q)[:100]}\")\n```\n\n## Step 5: Build the dashboard\n\nCreate `dashboard.py`\n\n:\n\n``` python\nimport sqlite3\nimport pandas as pd\nimport streamlit as st\n\nst.title(\"LLM API Spend\")\n\nwith sqlite3.connect(\"usage.db\") as con:\n    df = pd.read_sql_query(\"SELECT * FROM usage\", con)\ndf[\"ts\"] = pd.to_datetime(df[\"ts\"], unit=\"s\")\n\nc1, c2, c3 = st.columns(3)\nc1.metric(\"Total spend\", f\"${df.cost_usd.sum():.4f}\")\nc2.metric(\"Requests\", len(df))\nc3.metric(\"Avg cost / request\", f\"${df.cost_usd.mean():.4f}\")\n\nst.subheader(\"Spend by model\")\nst.bar_chart(df.groupby(\"model\")[\"cost_usd\"].sum())\n\nst.subheader(\"Cost per request over time\")\nst.scatter_chart(df, x=\"ts\", y=\"cost_usd\", color=\"model\")\n\nst.subheader(\"Raw log\")\nst.dataframe(df.sort_values(\"ts\", ascending=False))\n```\n\nLaunch it:\n\n```\nstreamlit run dashboard.py\n```\n\n## Verify it works\n\n`python demo.py`\n\nshould print one answer per provider, then confirm the rows landed:\n\n``` python\npython -c \"import sqlite3; [print(r) for r in sqlite3.connect('usage.db').execute(\n    'SELECT provider, model, input_tokens, output_tokens, round(cost_usd, 6) FROM usage')]\"\n```\n\nExpected (token counts will vary):\n\n```\n('openai', 'gpt-5.6-terra', 18, 24, 0.000324)\n('anthropic', 'claude-opus-5', 16, 310, 0.00783)\n('google', 'gemini-3.6-flash', 11, 245, 0.001854)\n```\n\n`streamlit run dashboard.py`\n\nprints `Local URL: http://localhost:8501`\n\nand 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.\n\n## Troubleshooting\n\n— the key isn't in this shell.`openai.AuthenticationError: Error code: 401 ... Incorrect API key provided`\n\n`export OPENAI_API_KEY=...`\n\nin 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'}`\n\n`ANTHROPIC_API_KEY`\n\nis 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.`\n\n[Google AI Studio](https://aistudio.google.com), not Google Cloud Console; make sure`GEMINI_API_KEY`\n\nholds an AI Studio key.when opening the dashboard — you ran Streamlit before any calls were logged. Run`sqlite3.OperationalError: no such table: usage`\n\n`python demo.py`\n\nfirst so`usage.db`\n\nexists.\n\n## Next steps\n\n- Log cached-token counts (\n`cache_read_input_tokens`\n\non Anthropic,`total_cached_tokens`\n\non 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\n`SUM(cost_usd)`\n\nbefore 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.\n\n## Sources & further reading\n\n-\n[API Pricing](https://developers.openai.com/api/docs/pricing)— developers.openai.com -\n[Developer quickstart](https://developers.openai.com/api/docs/quickstart)— developers.openai.com -\n[Gemini API Pricing](https://ai.google.dev/gemini-api/docs/pricing)— ai.google.dev -\n[Interactions API reference](https://ai.google.dev/api/interactions-api)— ai.google.dev -\n[Pricing](https://platform.claude.com/docs/en/pricing)— platform.claude.com -\n[2026 release notes](https://docs.streamlit.io/develop/quick-reference/release-notes/2026)— docs.streamlit.io\n\n[Priya Nair](https://sourcefeed.dev/u/priya_nair)· AI & Developer Experience Writer\n\nPriya 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.\n\n## Discussion 0\n\nNo comments yet\n\nBe the first to weigh in.", "url": "https://wpnews.pro/news/track-your-llm-api-spend-with-a-python-token-usage-dashboard", "canonical_source": "https://sourcefeed.dev/a/track-your-llm-api-spend-with-a-python-token-usage-dashboard", "published_at": "2026-08-09 17:41:12+00:00", "updated_at": "2026-08-09 17:45:36.850514+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "artificial-intelligence"], "entities": ["Priya Nair", "OpenAI", "Anthropic", "Gemini", "Streamlit", "SQLite", "gpt-5.6-terra", "claude-opus-5"], "alternates": {"html": "https://wpnews.pro/news/track-your-llm-api-spend-with-a-python-token-usage-dashboard", "markdown": "https://wpnews.pro/news/track-your-llm-api-spend-with-a-python-token-usage-dashboard.md", "text": "https://wpnews.pro/news/track-your-llm-api-spend-with-a-python-token-usage-dashboard.txt", "jsonld": "https://wpnews.pro/news/track-your-llm-api-spend-with-a-python-token-usage-dashboard.jsonld"}}