A lightweight OpenRouter proxy service built with TypeScript + Hono + Zod, providing three core endpoints:
- List currently free models on OpenRouter
- Access the strongest model via OpenRouter auto-routing
- Access a specified free model with validation
Includes a built-in browser playground for manual API testing.
- Dynamic model discovery — reads from OpenRouter
/api/v1/modelsinstead of maintaining a static model list - Auto-routing — uses OpenRouter's official
openrouter/autorather than hand-picking "the best" model - Thin proxy — Zod handles input validation only; no database, all state lives in process memory
- Cost guard — validates that a model is actually free before forwarding, preventing accidental paid usage
- Node.js 22+
- A valid
OPENROUTER_API_KEY
cp .env.example .env
# Edit .env and fill in your OPENROUTER_API_KEY
npm install
npm run devProduction build:
npm run build
npm startThe service listens on http://localhost:3000 by default.
Open the playground at:
http://localhost:3000/
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Server port |
OPENROUTER_API_KEY |
- | OpenRouter API Key (required for chat endpoints) |
OPENROUTER_BASE_URL |
https://openrouter.ai/api/v1 |
OpenRouter API base URL |
APP_NAME |
free-open-router |
App name passed to OpenRouter |
SITE_URL |
(empty) | Site URL passed to OpenRouter |
MODELS_CACHE_TTL_MS |
300000 |
Model list cache TTL in milliseconds |
UPSTREAM_TIMEOUT_MS |
60000 |
Upstream request timeout in milliseconds |
CORS_ORIGIN |
* |
Allowed CORS origin |
Returns the current list of free models.
Optional query parameter:
refresh=1— force refresh the model cache
Example:
curl http://localhost:3000/api/v1/models/freeForwards requests to OpenRouter's openrouter/auto, which automatically selects the strongest/most suitable model.
The request body is compatible with OpenAI / OpenRouter chat completions format. Most parameters are passed through as-is; the model field is overridden to openrouter/auto on the server side.
Example:
curl http://localhost:3000/api/v1/chat/completions/strongest \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "user", "content": "Introduce yourself" }
]
}'Access a specified free model. The request body must include a model field. The service validates that the model is currently free before forwarding to OpenRouter.
Example:
curl http://localhost:3000/api/v1/chat/completions/free \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek/deepseek-chat-v3-0324:free",
"messages": [
{ "role": "user", "content": "Write a hello world" }
]
}'| Endpoint | Description |
|---|---|
GET / |
Built-in Playground UI |
GET /api |
API route summary |
GET /api/healthz |
Health check |
stream: truetransparently proxies OpenRouter's SSE streaming response- If OpenRouter's model directory is temporarily unavailable but a local cache exists, the service falls back to the stale cache
- Free model detection is based on whether all
pricingfields returned by OpenRouter are0 - No database is used; all state is cached in-process
npm testMIT