Realtime token & cost tracker for every AI you use — multi-provider, multi-model, multi-app.
Gentrek is a proxy gateway + live dashboard. Point your apps' SDK base URL at Gentrek, and every call (streaming included) is metered the moment it finishes: tokens, cache reads/writes, reasoning tokens, latency, TTFT, and estimated cost — per provider, per model, per app.
| Page | What it shows |
|---|---|
/ |
Dashboard — hero cost, KPI tiles, stacked tokens/cost over time, per-provider cards, per-model & per-app breakdowns, live call feed |
/flow |
Live flow map — every call animates from its app, through the gateway, to its provider |
/settings |
API keys (stored server-side, masked in UI), connection snippets, data-safety notes |
npm install
npm run dev # http://localhost:3000Open the dashboard and press Seed last hour to see it working with synthetic data
(tagged demo), or Start live traffic for a continuous simulation. Clear all data
removes everything.
- Give the gateway a key (any one of these):
- paste it on the Settings page (stored in
.data/keys.json, localhost-only writes), or - create
.env.localand set e.g.ANTHROPIC_API_KEY(see Configuration below), or - keep the key in your app — the gateway forwards your app's own auth header untouched.
- paste it on the Settings page (stored in
- Point the SDK at the gateway (copy-paste snippets are on the Settings page):
// Anthropic
new Anthropic({ baseURL: "http://localhost:3000/api/v1/anthropic",
defaultHeaders: { "x-gentrek-app": "my-app" } });
// OpenAI
new OpenAI({ baseURL: "http://localhost:3000/api/v1/openai/v1",
defaultHeaders: { "x-gentrek-app": "my-app" } });
// Google Gemini
new GoogleGenAI({ httpOptions: { baseUrl: "http://localhost:3000/api/v1/google",
headers: { "x-gentrek-app": "my-app" } } });
// OpenRouter (also asks OpenRouter for its real dollar cost per call)
new OpenAI({ baseURL: "http://localhost:3000/api/v1/openrouter/api/v1", ... });
// Any OpenAI-compatible endpoint (Groq, DeepSeek, Ollama, vLLM) — set CUSTOM_BASE_URL
new OpenAI({ baseURL: "http://localhost:3000/api/v1/custom/v1", ... });x-gentrek-app / x-gentrek-tag are optional attribution headers.
Settings has an Your own providers panel: give any AI endpoint a name and base URL and it becomes a first-class provider with its own gateway route, colour, logo and API key. One-click presets cover NVIDIA NIM, Kimi (Moonshot), DeepSeek, Groq, Mistral, xAI, Together and a local Ollama. Pick the dialect so the gateway knows how to read token usage from the response:
| Dialect | Use for |
|---|---|
| OpenAI compatible | almost everything: NVIDIA NIM, Kimi, DeepSeek, Groq, Mistral, xAI, Together, vLLM, Ollama |
| Anthropic Messages API | anything speaking /v1/messages |
| Google Gemini API | anything speaking generateContent |
A provider added as "Kimi" is served at /api/v1/kimi/v1 and stores its key under that id.
Models missing from data/pricing.json are still tracked; their cost reads n/a until you add
prices for them.
POST usage events directly:
curl -X POST http://localhost:3000/api/ingest -H "content-type: application/json" \
-d '{"provider":"anthropic","model":"claude-opus-5","input":1200,"output":300,"app":"batch-job"}'| Route | Purpose |
|---|---|
ALL /api/v1/{provider}/{...path} |
The metering proxy (anthropic, openai, google, openrouter, custom) |
POST /api/ingest |
Push usage events (single or array, max 5000) |
GET /api/stats?range=1h |
Aggregates: totals, by provider/model/app, time series |
GET /api/stream |
SSE feed of usage events as they happen |
GET /api/events?format=csv |
Raw events (JSON or CSV export) · DELETE wipes the log |
GET/POST/DELETE /api/keys |
Key management (writes localhost-only) |
GET /api/pricing |
The active pricing table |
POST /api/demo |
Seed synthetic traffic / toggle live simulation |
data/pricing.json — 63 models across Anthropic, OpenAI, Google Gemini, and OpenRouter,
verified against official pricing pages on 2026-08-28. Costs are split per token class
(input / output / cache read / cache write). OpenRouter calls prefer the real dollar cost
the API reports over the table. Edit the file any time — it hot-reloads, no restart needed.
Calls whose model isn't in the table are flagged unpriced rather than silently $0.
Set the Supabase env vars (see SETUP-CLOUD.md) and Gentrek becomes a multi-user service:
passwordless sign-in (Google / GitHub SSO or email magic link), one isolated workspace per
user (Postgres row-level security), and gateway calls authenticated by hashed gtk_…
workspace tokens. Leave the vars unset and everything stays single-user local.
Each user chooses how their provider key is handled. Save it and it is encrypted with
AES-256-GCM under GENTREK_ENCRYPTION_KEY, which lives only in the environment, so their
app then sends nothing but its workspace token. Leave it out and the app sends its own key
per request and nothing is stored. Both are fully supported.
- Prompts and responses are never stored — only token counts and call metadata.
- Local mode: events in
.data/events.jsonl, keys in.data/keys.json, both gitignored. Keys are masked in the UI and never sent to the browser. - Cloud mode: events in Postgres behind row-level security, so one workspace cannot read another's. A saved provider key is encrypted at rest and its table has no browser-readable policy at all.
- The gateway never returns a wildcard CORS header, and never spends a saved key for a browser origin it does not vouch for.
- Cost figures are estimates for awareness; the provider's billing page is the source of truth.
No .env file is committed. Create .env.local locally, and set the same
names in your host's dashboard when you deploy.
| Variable | Needed for | Notes |
|---|---|---|
ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, OPENROUTER_API_KEY |
local mode | Optional. Your app can send its own key instead |
NEXT_PUBLIC_SUPABASE_URL |
cloud mode | Project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
cloud mode | Publishable key, safe for browsers |
SUPABASE_SERVICE_ROLE_KEY |
cloud mode | Secret. Server only, bypasses row level security |
GENTREK_ENCRYPTION_KEY |
letting users save provider keys | 32 random bytes, base64. Never rotate it once keys are stored, or they become unreadable |
GENTREK_ALLOWED_ORIGINS |
optional | Comma separated. Only if a browser app calls the gateway directly |
GENTREK_GATEWAY_SECRET |
optional | Required on every call when set. Use it before exposing a local instance on a network |
GENTREK_RETENTION_DAYS |
optional | Raw events older than this are pruned. Default 90, 0 disables |
GENTREK_ALLOW_DEMO |
optional | Set to 0 to refuse synthetic seed traffic |
GENTREK_DATA_DIR |
optional | Where local state lives. Default .data |
Generate an encryption key with:
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"npm test # SSE parsing, per-provider usage extraction, pricing math