free-llm-radar
Live radar of every free LLM endpoint on OpenRouter — uptime, quantization, context, throughput, drift. Real public data, refreshed every 15 minutes. No mocks. No keys.
A small Node.js dashboard that pulls the public OpenRouter catalog every 15 minutes, fetches per-model provider stats every hour, persists every snapshot in SQLite, and exposes both a JSON API and a dark-themed SPA at /free-llm-radar. The product is the answer to a question developers ask every week:
"Which models are actually free right now, who's serving them, and which providers are up?"
---
Data sources (the only ones, unauthenticated, no scraping)
| Endpoint | Refresh | Used for |
|---|---|---|
| GET https://openrouter.ai/api/v1/models | every 15 min | model catalog, pricing, modalities, context window, supported_parameters, knowledge cutoff, expiration |
| GET https://openrouter.ai/api/v1/models/{id}/endpoints | every 60 min, per free model | per-provider uptime (5m/30m/1d), latency, throughput, quantization, status, max tokens |
A model is considered free if pricing.prompt === "0" AND pricing.completion === "0" in the OpenRouter response. As of writing there are ~28 such models in the catalog; that count drifts daily as providers come and go.
There is no fallback list, no seed JSON, no Math.random() anywhere in the codebase. If both API calls fail at boot, the dashboard renders an honest "awaiting first refresh" message and the cron keeps trying.
What you get
- Leaderboard tab — every free model, sortable, filterable by capability (tools, json, reasoning, vision, audio, file) and minimum context, with a color-coded Best uptime 24h bar (max across providers).
- Drift tab — chronological feed of
added,removed,went_free,went_paid,provider_added,provider_removed,quant_changed,supported_params_changedevents. Default window: last 7 days. - Providers tab — rollup of how many free models each provider serves and their average 24h uptime.
- Detail slide-over — click any model row to see the full provider list with quantization, latency, throughput, supported parameters and direct links to OpenRouter and Hugging Face.
- Public JSON API — the same data the SPA reads is exposed at
/free-llm-radar/api/*. Caller code is welcome to scrape it.
Quickstart
cp .env.example .env
npm install
node server.js
# open http://localhost:4750/free-llm-radar/
The first catalog refresh kicks off ~1.5s after boot if the database has no recent run. Endpoint stats follow.
API
All responses come wrapped as { data, generated_at, source: 'openrouter.ai/api/v1', ... }.
| Method | Path | Purpose |
|---|---|---|
| GET | /free-llm-radar/health | Liveness probe. Always 200 once the server is up. |
| GET | /free-llm-radar/api/summary | Counts, last-refresh timestamps, recent fetch run log. |
| GET | /free-llm-radar/api/models?free=1&capability=tools&minctx=128000&search=qwen&sort=reliability&limit=200 | Paginated list of models with embedded provider endpoints and reliability score. |
| GET | /free-llm-radar/api/models/{author}/{slug} | Full detail for one model id (URL-escape : as %3A). |
| GET | /free-llm-radar/api/drift?since=ISO&kind=added&limit=200 | Chronological drift feed. |
| GET | /free-llm-radar/api/providers | Per-provider rollup (models served, avg 24h uptime, total context). |
| GET | /free-llm-radar/api/leaderboard?metric=uptime|context|providers&limit=20 | Top-N by chosen metric. |
Configuration (.env)
| Variable | Default | Notes |
|---|---|---|
| PORT | 4750 | HTTP port. |
| BASE_PATH | /free-llm-radar | URL prefix every route is mounted under. |
| DB_PATH | ./data/free-llm-radar.db | SQLite file location. WAL mode is forced. |
| OPENROUTER_API_BASE | https://openrouter.ai/api/v1 | Override only for local mocking. |
| CATALOG_CRON | /15 | Catalog refresh schedule (UTC). |
| ENDPOINTS_CRON | 5 * | Per-model endpoint refresh schedule. |
| HTTP_TIMEOUT_MS | 30000 | Per-request timeout. |
| ENDPOINT_FETCH_GAP_MS | 1000 | Sleep between provider-stat batches. |
| ENDPOINT_FETCH_BATCH | 5 | Concurrency for the per-model endpoint loop. |
Data model (SQLite)
models— canonical row per OpenRouter model id;removed_atis set when the model disappears from the catalogendpoints— append-only history of per-provider snapshotsendpoints_current— latest snapshot per(model_id, provider_name, tag); the API reads from heredrift_events— every detected change, with a JSONdetailpayloadfetch_runs— log of every catalog/endpoints invocation (success, error, counts)meta— small KV table for last-refresh timestamps
See db.js for the exact CREATE TABLE statements.
Deployment
The product ships with a DEPLOY_MANIFEST.json that the RNDLAB watcher consumes (rsync → systemd → nginx → showcase). Health endpoint at /free-llm-radar/health is auth-free and returns 200 within a few hundred ms of boot, so the smoke check passes before the first catalog fetch finishes.
No secret env vars are required. The OpenRouter /models and /models/{id}/endpoints endpoints are unauthenticated.
License
UNLICENSED — internal Cowork experiment, deployed at https://holyai.me/free-llm-radar/.