- Go 92.6%
- Go Template 3.8%
- Dockerfile 2.6%
- Makefile 1%
Chart.yaml/values.yaml/templates never reached git, so helm package failed with 'Chart.yaml file is missing' and chart-* releases were impossible. Whitelist chart/**/*.yaml. |
||
|---|---|---|
| .woodpecker | ||
| chart/routerai-exporter | ||
| cmd/routerai-exporter | ||
| examples | ||
| internal | ||
| .gitignore | ||
| .golangci.yml | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
routerai-exporter
Warning
This is a vibecoded project. Built end-to-end by an AI agent (Hermes, Nous Research) from a conversational spec, with human review limited to architecture decisions and API contracts. It works — CI is green and the live gateway verifies the numbers — but treat it as unaudited: read the relay/proxy code before pointing production credentials at it, and don't trust the defaults blindly.
Prometheus exporter for routerai.ru API spend — money per API key, money and tokens per model, account balance.
Sibling of keen-exporter (same structure, config conventions and CI).
What it does
Two independent data paths:
-
Passive scraping (always on). Polls the gateway account API on every scrape:
/v1/credits— account balance;/v1/keys— spend, monthly spend and limits of every named API key (needs the account master key; with only a regular key it falls back to/v1/key, the self view).
-
Relay mode (opt-in,
relay: true). A transparent OpenAI-compatible proxy mounted at/relay/<account>/v1/...: point your OpenAI/Anthropic SDKs at it instead ofhttps://routerai.ru/api/v1and the exporter reads the exact cost the gateway itself reports (usage.costin the response body, including the final SSE chunk), aggregating per model and provider. No scraping, no guessing from price lists — every sum comes from the gateway.Authorization passes through: clients may use their own routerai key (per-key attribution on the routerai side keeps working); when none is sent, the account default key is applied.
Metrics
All money metrics are in credits (the gateway's billing unit; a
credit is billed in roubles today) and carry a currency="rub" label.
Token/latency/meta metrics have no currency label.
Scrape & exporter meta
| Metric | Labels | Meaning |
|---|---|---|
routerai_up |
account | 1 if the account API fetch succeeded |
routerai_scrape_error |
account, reason | 1 on failure; timeout|auth_failed|connection|http_5xx |
routerai_scrape_duration_seconds |
account | fetch duration |
routerai_exporter_build_info |
version, commit, go_version | build info |
Balance & per-key spend (passive)
| Metric | Labels | Meaning |
|---|---|---|
routerai_account_credits |
account, currency | account balance |
routerai_key_usage_credits_total |
account, currency, key | lifetime spend of a key (counter) |
routerai_key_usage_monthly_credits_total |
account, currency, key | current-month spend of a key |
routerai_key_limit_credits |
account, currency, key | monthly limit; 0 = unlimited |
routerai_key_limit_remaining_credits |
account, currency, key | remaining limit; absent for unlimited keys (the gateway reports garbage there) |
routerai_key_disabled |
account, key | 1 if the key is disabled |
routerai_key_info |
account, key, limit_reset, created_at | key identity |
Per-model spend (relay mode only; appears with data)
| Metric | Labels | Meaning |
|---|---|---|
routerai_model_cost_credits_total |
account, currency, model, provider | exact spend as reported by the gateway (counter) |
routerai_model_requests_total |
account, model, provider | completed requests |
routerai_model_prompt_tokens_total |
account, model, provider | prompt tokens (incl. cached) |
routerai_model_completion_tokens_total |
account, model, provider | completion tokens (incl. reasoning) |
routerai_model_prompt_cached_tokens_total |
account, model, provider | cached prompt tokens |
routerai_model_reasoning_tokens_total |
account, model, provider | reasoning tokens |
routerai_model_request_duration_seconds_{bucket,sum,count} |
account, model, provider[, le] | request latency histogram |
routerai_relay_requests_total |
account, outcome | passthrough attempts: ok, error_upstream, error_relay |
routerai_relay_accounted_credits_total |
account, currency | total money accounted by the relay since start |
Relay counters are exporter-lifetime: they reset on restart (standard Prometheus counter handling) and do not persist.
Configuration
See examples/config.yaml. Secrets interpolate
from the environment: ${VAR}, ${VAR:-fallback}, literal $ as $$;
unknown keys are a hard error.
server:
listen_address: ":9913"
metrics_path: /metrics
log:
level: info
format: logfmt
routerai:
base_url: https://routerai.ru/api
timeout: 4s
relay:
prefix: /relay
max_idle_stream_timeout: 10m
accounts:
- name: main
key: ${ROUTERAI_API_KEY} # regular key: balance + self metrics + relay default
master_key: ${ROUTERAI_MASTER_KEY} # optional: per-key spend for all keys
relay: true
Auth matrix (verified live against the gateway):
| Credential | /v1/credits |
/v1/key |
/v1/keys |
/v1/chat/completions |
|---|---|---|---|---|
| regular key | ✅ | ✅ | ❌ 401 | ✅ |
| master key | ❌ 401 | ❌ 401 | ✅ | ❌ |
A master-only account works: balance is absent, per-key spend is present.
Usage
routerai-exporter serve --config routerai-exporter.yaml
routerai-exporter validate-config --config routerai-exporter.yaml
routerai-exporter probe --account main --config routerai-exporter.yaml # one collect to stdout
Scrape /metrics, and — with relay enabled — point clients at
http://<host>:9913/relay/<account>/v1 (OpenAI base URL) instead of
https://routerai.ru/api/v1.
CI
Woodpecker (Forgejo webhooks, k8s backend), .woodpecker/:
| Workflow | Trigger | What it does |
|---|---|---|
test.yml |
push to main, tags | golangci-lint v2.14 (go1.27 support), go test -race, govulncheck |
docker-edge.yml |
push to main | multi-arch image → cr.ppvn.ru/wailorman/routerai-exporter/app (edge + sha tags) |
docker-release.yml |
tag v* |
multi-arch image → same repo (vX.Y.Z + latest) |
chart.yml |
tag chart-* |
Helm chart → oci://cr.ppvn.ru/charts/routerai-exporter |
Versioning: app releases are vX.Y.Z tags (docker-release);
chart releases are independent — chart-X.Y.Z tags (chart.yml
derives the chart version from the tag, appVersion is maintained in
Chart.yaml and may lag/lead the app).
Build cache: per-branch tags in
cr.ppvn.ru/wailorman/routerai-exporter/build-cache — cache_from
lists the branch's entry with a fallback to main's (BuildKit ignores
misses), cache_images writes the current branch's entry.
Registry auth via global Woodpecker secrets LOCAL_REGISTRY_USERNAME /
LOCAL_REGISTRY_PASSWORD; buildx runs privileged via server-wide
WOODPECKER_PLUGINS_PRIVILEGED.
Helm chart
chart/routerai-exporter — generic chart (no environment values baked
in; the deployment side supplies tag, host and credentials).
Monitoring resources — all disabled by default, under one
monitoring.enabled switch plus per-flavor toggles:
monitoring.serviceMonitor.enabled— prometheus-operatorServiceMonitorCRD;monitoring.vmServiceScrape.enabled— VictoriaMetrics operatorVMServiceScrapeCRD;monitoring.grafanaDashboard.enabled— Grafana sidecar dashboard (ConfigMap with thegrafana_dashboard: "1"label) with a default spend dashboard: balance, per-key monthly spend, limit utilization, per-model spend rate, tokens, latency, relay errors.
Development
make all # lint + test + build
make test # go test ./... -race
make image # local multi-arch image build
License
MIT — see LICENSE.