keymeter
A live dashboard for one LLM gateway API key: how much of its budget is spent, how fast it is going, and when it will run out. It works with a LiteLLM proxy or OpenRouter, in the browser or in the terminal.
LiteLLM's admin UI needs an admin key. keymeter only needs the key you were handed, which is often all you have: a team key at work, a key for a hackathon or a course. It polls the gateway's read-only key endpoint (no tokens spent) and shows:
- spend vs. budget, what is left, and when the budget resets
- the burn rate over the last 15 minutes, when the budget runs out at that rate, and the projected spend at reset
- charts of spend over time and spend per interval, with a table view
- the key's expiry, and on LiteLLM spend per model, per-model budgets, rate limits, allowed models and blocked state
- an event log, and optional sound and desktop alerts when the status changes or a budget passes 80% or 95%
The key stays in the keymeter process. The browser only ever sees a masked version of it.
Install
pipx install keymeter # or: uv tool install keymeter
keymeter needs Python 3.10 or newer.
Quick start
LiteLLM proxy
export KEYMETER_URL=https://llm.example.com # your proxy
export KEYMETER_KEY=sk-...
keymeter web
OpenRouter
export KEYMETER_KEY=sk-or-v1-...
keymeter web
Then open http://localhost:8765. keymeter picks the gateway by itself: OpenRouter when the URL's
host is openrouter.ai or a subdomain of it, or, if no URL is set, when the key starts with sk-or-;
LiteLLM otherwise.
So OpenRouter needs no URL, and a bare https://openrouter.ai works too.
On Windows PowerShell, set the variables with $env:KEYMETER_KEY = "sk-...". You can also put
them in a .env file instead (see Configuration).
Commands
| Command | |
|---|---|
keymeter web |
browser dashboard on http://localhost:8765 |
keymeter tui |
live dashboard in the terminal; on LiteLLM it also shows the team's budget and keys when the key may read them |
keymeter tui --once |
print one snapshot and exit; exits with code 1 when the status is not OK, like json |
keymeter json |
print one snapshot as JSON, for scripts and cron jobs; exits with code 1 when the status is not OK (the key or its team over budget or blocked, the key expired, or the gateway unreachable or answering with an error) |
Options (keymeter COMMAND -h lists them per command):
| Option | Default | |
|---|---|---|
--gateway |
auto |
litellm, openrouter, or auto to pick from the URL's host, or from the key when no URL is set |
--url |
depends on the gateway | gateway base URL; http://localhost:4000 for LiteLLM, https://openrouter.ai/api for OpenRouter. A trailing /v1 is fine, and so is a bare https://openrouter.ai. |
--env FILE |
.env |
where to read settings from; ./.env is skipped when KEYMETER_KEY is already set in the environment |
-i, --interval |
15 |
seconds between polls (web, tui) |
--window |
15 |
burn-rate window in minutes (web, tui) |
--warn / --crit |
0.8 / 0.95 |
alert thresholds, as a fraction of a budget (web, tui) |
--log FILE.csv |
also append every poll to a CSV file (web, tui) |
|
--host / --port |
127.0.0.1 / 8765 |
where the dashboard listens (web) |
--no-team |
skip LiteLLM's /team/info (tui, json) |
|
--no-bell |
don't beep on alerts (tui) |
Configuration
keymeter reads three settings, from the environment first and then from a .env file in the
current directory (or the file given with --env). An empty variable counts as unset.
| Variable | |
|---|---|
KEYMETER_KEY |
the API key to watch (required) |
KEYMETER_URL |
gateway base URL |
KEYMETER_GATEWAY |
auto, litellm or openrouter |
Copy .env.example to .env to start.
keymeter reads ./.env only when KEYMETER_KEY is not set in the environment (a file you name with
--env is always read). That way a .env in whatever directory you happen to be in, such as a cloned
repository, can't send your key to another server.
There is no --key option, because a key typed on the command line ends up in your shell history.
The file's other NAME=value lines go into keymeter's environment too (a variable already set there
wins unless it is empty). Python's HTTP client takes its proxy from the environment, so HTTPS_PROXY,
HTTP_PROXY or NO_PROXY in the file apply to the requests to the gateway, and a proxy set that way
can read the key when the gateway URL is plain http://. Pass --env only files you trust.
What each gateway reports
| LiteLLM | OpenRouter | |
|---|---|---|
| Spend, budget, reset time, burn rate, projections | yes | yes |
| Key expiry | yes | yes |
| Spend per model, per-model budgets | yes | no |
| Rate limits, allowed models, blocked state | yes | no |
Team budget and team keys (tui, json) |
yes | no |
keymeter shows how much of a per-model budget is used only on LiteLLM 1.90 or newer, which reports the spend in each budget's period. Older versions report only all-time spend per model, so keymeter shows the limit alone, with no percentage and no alerts.
On OpenRouter the budget is the key's credit limit. The spend shown is what counts against that limit in the current period. Limits reset at 00:00 UTC: daily, weekly on Monday, or on the 1st of the month. A key without a limit shows its all-time usage and no budget.
Running it on a server
keymeter web listens on 127.0.0.1 only. To watch it from your laptop, forward the port over SSH:
# on the server
keymeter web
# on your laptop
ssh -N -L 8765:localhost:8765 you@server
Then open http://localhost:8765 on the laptop. Because the browser sees localhost, desktop
notifications work as well (browsers only allow them over HTTPS or on localhost).
On 127.0.0.1, keymeter answers only requests whose Host header is localhost, 127.0.0.1 or
[::1] (with any port), or that have no Host header. This stops another website from reading the
dashboard through DNS rebinding. A reverse proxy in front of it has to send one of those, such as
Host: localhost.
To keep it running after you log out, use tmux, a systemd service, or
nohup keymeter web > keymeter.log 2>&1 &.
keymeter web --host 0.0.0.0 serves the dashboard on every interface instead. There is no login,
so anyone who can reach the port can see the key's spend, budget and model usage (not the key
itself) and press "Poll now". If you do this, allow only your own IP in the firewall.
How it works
- keymeter calls
GET /key/infoon LiteLLM (plus/team/infofortuiandjson), orGET /api/v1/keyon OpenRouter. These endpoints are read-only and cost nothing. - The burn rate is the spend added over the burn-rate window, per hour. When spend goes down, keymeter takes it as a budget reset and starts measuring again.
- When the gateway stops answering, polls back off to at most once a minute. The dashboard keeps showing the last numbers it got and says how old they are.
- The charts' history lives in memory for 24 hours and starts over when keymeter restarts. Use
--logto keep a permanent record. - The bell button in the browser turns on alerts: a short sound, plus a desktop notification when the browser allows it.
- In the web UI, switching the theme, the range, between chart and table view, or alerts on and off crossfades. When new data arrives, the big numbers count to their new value and the bars and charts glide instead of jumping; status banners fade in and out, and the dashboard fades in when it replaces the "No quota data yet" card. With reduced motion turned on in your system, only the looping spinner and pulse stop.
Development
git clone https://github.com/hoanghero125/keymeter
cd keymeter
uv sync
uv run pytest
uv run ruff check
Without uv, run python -m venv .venv, activate it, then pip install -e . --group dev (pip 25.1
or newer).
The web UI is plain HTML, CSS and JavaScript in src/keymeter/static, with no build step.
To support another gateway, add an adapter in src/keymeter/gateways/. It fetches the key's data
and translates it into the field names the rest of keymeter uses (LiteLLM's); see openrouter.py
for a small example. Then register it in gateways/__init__.py: add it to GATEWAYS and to
create(), which builds the adapter, and to detect() if auto should pick it.
Releasing
- Set
__version__insrc/keymeter/__init__.pyand add the release toCHANGELOG.md. - Commit and tag, then push the commit and the tag together:
git tag v0.1.1 && git push origin main v0.1.1.
The Release workflow tests and builds the package, then publishes it to PyPI with trusted
publishing, so no API token is involved. The trusted publisher (on PyPI, under the project's
Manage → Publishing) names owner hoanghero125, repository keymeter, workflow release.yml and
environment pypi, the GitHub environment the workflow runs in. If you rename the workflow file or
the environment, update the publisher to match.
License
MIT. keymeter is not affiliated with LiteLLM (BerriAI) or OpenRouter.
Metadata
Release files for keymeter 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| keymeter-0.1.1.tar.gz | 69.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| keymeter-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 124.7 kB
Release files / keymeter-0.1.1.tar.gz
| Download URL | keymeter-0.1.1.tar.gz |
|---|---|
| Size | 69.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
794f382d89c27c1898afd91b5f9762814cacf4f4c73b768e95a092b71df09166
|
|
BLAKE2b-256 checksum How to use checksums |
37572ca71e6adaffa60a2dc8711064eb6ab64be26e255f3384415e6db52c9a4f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency logRelease files / keymeter-0.1.1-py3-none-any.whl
| Download URL | keymeter-0.1.1-py3-none-any.whl |
|---|---|
| Size | 54.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
63d1171f7ac3ef75c16205e2be220d04a0107e8b144bb1e2f5236f1f9fba12ea
|
|
BLAKE2b-256 checksum How to use checksums |
59e70f51a443c12cf29c4371884e3895ab22d5b7cfddb23ac5b29ef92bda1ffe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency log