Freeway: self-hosted OpenAI-compatible gateway for free-tier providers

One endpoint in front of every free LLM tier, with automatic failover and no
provider key ever leaving the box.

Providers are data, not code: adding one is a JSON file plus an env var, never
a TypeScript change. Ships 12 providers pre-written; `verify-provider --apply`
derives the live model list from your own key.

- core: registry, bucketed quota meter (rolling + UTC-calendar windows),
  key-pool store, 6-step model router with diagnostic rejection reasons
- gateway: node:http, failover that commits to a stream only after 200 headers,
  SSE passthrough with usage sniffing, virtual keys with admin/inference roles
- context engine: sqlite sessions and a 5-rung refit ladder, so a 128k thread
  keeps working when it fails over to an 8k model
- cache: exact (sha256, safe-by-default) + opt-in semantic (flat Float32Array)
- guard: inbound limits, quota forecasting, limit calibration from upstream
  headers, key health, secret/PII/injection scanning
- dashboard: one self-contained HTML file, live SSE request log, token usage
- sdk: zero-dep typed client, drop-in for the OpenAI SDK

Zero runtime dependencies. 326 tests, 27-check smoke suite against a scripted
mock upstream so routing is testable without spending quota.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Aryan SinghandClaude Opus 5 committed 2026-08-16 22:41:49 -05:00
commit cb985dcf9c
86 files changed
+16350

No files matched your search

+142
View File
@@ -0,0 +1,142 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://freeway.local/schemas/provider.json",
"title": "Freeway provider spec",
"description": "One free-tier API provider, as data. Adding a provider means dropping one of these into providers/ and setting an env var — never editing TypeScript. Files whose name starts with _ are skipped by the loader, which is why this schema can live here.",
"type": "object",
"required": ["id", "baseUrl", "auth"],
"additionalProperties": false,
"properties": {
"$schema": { "type": "string" },
"id": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]*$",
"description": "Short slug. Becomes the first half of every model ref, e.g. `groq/llama-3.3-70b`."
},
"label": { "type": "string", "description": "Human name for the dashboard. Defaults to id." },
"docs": { "type": ["string", "null"], "description": "API docs URL, linked from the provider card." },
"console": { "type": ["string", "null"], "description": "Where to get a key. Linked as 'get a key' on unconfigured cards." },
"enabled": { "type": "boolean", "default": true },
"priority": { "type": "number", "default": 50, "description": "Lower is tried first under the `priority` strategy." },
"adapter": { "enum": ["openai"], "default": "openai", "description": "Wire format. Only `openai` is implemented." },
"baseUrl": {
"type": "string",
"description": "OpenAI-compatible base, e.g. https://api.groq.com/openai/v1. May contain ${ENV_VAR}, which is how providers that embed an account id in the URL stay pure data.",
"examples": [
"https://api.groq.com/openai/v1",
"https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1"
]
},
"auth": {
"type": "object",
"required": ["type"],
"additionalProperties": false,
"properties": {
"type": {
"enum": ["bearer", "header", "query", "none"],
"description": "bearer → Authorization: Bearer <key>. header → a named header. query → a query param. none → no credential (a local Ollama)."
},
"header": { "type": ["string", "null"], "description": "Header name when type is `header`, e.g. x-api-key." },
"query": { "type": ["string", "null"], "description": "Query param name when type is `query`, e.g. key." },
"prefix": { "type": ["string", "null"], "description": "Overrides the `Bearer ` prefix. Set \"\" for a bare token." },
"envKeys": {
"type": "array",
"items": { "type": "string" },
"description": "Env vars to read keys from, in order. ONE variable may hold several comma-separated keys — that is the key pool, and quota is tracked per key.",
"examples": [["GROQ_API_KEY", "GROQ_API_KEY_2"]]
}
}
},
"limits": {
"type": "object",
"additionalProperties": false,
"description": "Free-tier ceilings. null means unknown or unlimited and is ALWAYS better than a guess — the guard layer overwrites nulls with values learned from upstream rate-limit headers.",
"properties": {
"rps": { "type": ["number", "null"], "exclusiveMinimum": 0, "description": "Requests per second (rolling)." },
"rpm": { "type": ["number", "null"], "exclusiveMinimum": 0, "description": "Requests per minute (rolling 60s)." },
"rpd": { "type": ["number", "null"], "exclusiveMinimum": 0, "description": "Requests per day (calendar, resets 00:00 UTC)." },
"tpm": { "type": ["number", "null"], "exclusiveMinimum": 0, "description": "Tokens per minute (rolling 60s)." },
"tpd": { "type": ["number", "null"], "exclusiveMinimum": 0, "description": "Tokens per day (calendar UTC)." },
"tpmo": { "type": ["number", "null"], "exclusiveMinimum": 0, "description": "Tokens per calendar month." },
"creditsPerDay": { "type": ["number", "null"], "exclusiveMinimum": 0, "description": "Abstract metered unit per day — Cloudflare bills free Workers AI in neurons." },
"creditsPerRequest": { "type": ["number", "null"], "exclusiveMinimum": 0, "description": "Fallback flat credit cost when a model has no per-token rates." }
}
},
"limitsSource": {
"enum": ["docs", "observed", "unverified"],
"default": "unverified",
"description": "Where the numbers above came from. Use `unverified` honestly — the dashboard badges it and nothing breaks."
},
"verifiedOn": { "type": ["string", "null"], "description": "ISO date the limits were last checked, e.g. 2026-08-16." },
"modelsEndpoint": {
"type": ["string", "null"],
"default": "/models",
"description": "Path for live model discovery, relative to baseUrl. null disables it (Cloudflare's OpenAI surface has no /models)."
},
"modelsPath": {
"type": "string",
"default": "data[].id",
"description": "Where ids live in the discovery response. Tiny grammar: `data[].id`, `models[].name`, `[].id`, `result[].name`. This is what keeps a non-OpenAI response shape a JSON edit.",
"examples": ["data[].id", "models[].name", "result[].name", "[].id"]
},
"dropParams": {
"type": "array",
"items": { "type": "string" },
"description": "Body params this provider rejects; stripped before forwarding. Find them by sending a request and reading the 400.",
"examples": [["frequency_penalty", "presence_penalty"]]
},
"headers": {
"type": "object",
"additionalProperties": { "type": "string" },
"description": "Extra request headers. Values may contain ${ENV_VAR}."
},
"notes": { "type": ["string", "null"], "description": "Anything the next reader needs — signup friction, quirks, why a number is what it is." },
"models": {
"type": "array",
"description": "Seed models. Prefer a handful you have actually verified over a long list you have not; live discovery fills in the rest.",
"items": {
"type": "object",
"required": ["id"],
"additionalProperties": false,
"properties": {
"id": { "type": "string", "description": "Upstream model id, exactly as the provider spells it. May contain slashes." },
"label": { "type": "string" },
"alias": {
"type": "array",
"items": { "type": "string" },
"description": "Short names clients can route by. Several providers sharing an alias is the point — that is what makes failover work.",
"examples": [["fast", "cheap"]]
},
"context": { "type": ["number", "null"], "exclusiveMinimum": 0, "description": "Context window in tokens. null when unpublished; the context engine then assumes a conservative default." },
"caps": {
"type": "array",
"items": { "enum": ["chat", "tools", "json", "vision", "reasoning", "embed", "code", "long-context"] },
"default": ["chat"],
"description": "Drives routing. A request with `tools` only goes to models declaring `tools`. `embed` models never serve chat and vice versa."
},
"priority": { "type": "number", "default": 50 },
"enabled": { "type": "boolean", "default": true },
"maxOutput": { "type": ["number", "null"], "exclusiveMinimum": 0 },
"credits": {
"type": "object",
"additionalProperties": false,
"description": "Per-model credit rates. Output tokens typically cost 7-10x input, and a big model ~10x a small one, so one flat number per provider is wrong by an order of magnitude.",
"properties": {
"perMTokIn": { "type": ["number", "null"], "exclusiveMinimum": 0 },
"perMTokOut": { "type": ["number", "null"], "exclusiveMinimum": 0 }
}
}
}
}
}
}
}
+19
View File
@@ -0,0 +1,19 @@
{
"$schema": "./_schema.json",
"id": "cerebras",
"label": "Cerebras",
"docs": "https://inference-docs.cerebras.ai/",
"console": "https://cloud.cerebras.ai/platform",
"priority": 15,
"baseUrl": "https://api.cerebras.ai/v1",
"auth": { "type": "bearer", "envKeys": ["CEREBRAS_API_KEY"] },
"limits": { "rpm": 30, "rpd": 14400, "tpm": 60000, "tpd": 1000000 },
"limitsSource": "docs",
"verifiedOn": "2026-08-16",
"modelsEndpoint": "/models",
"modelsPath": "data[].id",
"notes": "Largest daily token budget of the free tiers (~1M tokens/day), no card required. Cerebras reports windowed rate-limit headers (x-ratelimit-limit-tokens-day and friends), so calibration gets exact per-key numbers after the first request.",
"models": [
{ "id": "llama-3.3-70b", "label": "Llama 3.3 70B", "alias": ["fast", "big"], "context": 65536, "caps": ["chat", "tools", "json"], "priority": 10 }
]
}
+89
View File
@@ -0,0 +1,89 @@
{
"$schema": "./_schema.json",
"id": "cloudflare",
"label": "Cloudflare Workers AI",
"docs": "https://developers.cloudflare.com/workers-ai/models/",
"console": "https://dash.cloudflare.com/profile/api-tokens",
"enabled": true,
"priority": 20,
"adapter": "openai",
"baseUrl": "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1",
"auth": {
"type": "bearer",
"envKeys": ["CLOUDFLARE_API_TOKEN"]
},
"limits": {
"creditsPerDay": 10000,
"creditsPerRequest": 15
},
"limitsSource": "docs",
"verifiedOn": "2026-08-16",
"modelsEndpoint": null,
"modelsPath": "data[].id",
"dropParams": [],
"headers": {},
"notes": "Free tier is 10,000 neurons/day (confirmed on the pricing page), metered per token rather than per request. Per-model neuron rates come straight from Cloudflare's published table; note output tokens cost roughly 7-10x input, which is why rates are per-model rather than one flat number. limits.creditsPerRequest is only a fallback for models without published rates. Model ids contain '/', which is why provider/model pins split on the FIRST slash only. The OpenAI-compatible surface exposes /chat/completions and /embeddings but not /models, hence modelsEndpoint: null.",
"models": [
{
"id": "@cf/meta/llama-3.1-8b-instruct",
"label": "Llama 3.1 8B Instruct",
"alias": ["cheap"],
"context": 8192,
"caps": ["chat", "code"],
"priority": 30,
"credits": { "perMTokIn": 2457, "perMTokOut": 18252 }
},
{
"id": "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
"label": "Llama 3.3 70B Instruct (fp8 fast)",
"alias": ["big"],
"context": 24000,
"caps": ["chat", "tools", "code"],
"priority": 20,
"credits": { "perMTokIn": 26668, "perMTokOut": 204805 }
},
{
"id": "@cf/meta/llama-3.2-3b-instruct",
"label": "Llama 3.2 3B Instruct",
"alias": [],
"context": 8192,
"caps": ["chat"],
"priority": 40,
"credits": { "perMTokIn": 2457, "perMTokOut": 18252 }
},
{
"id": "@cf/mistralai/mistral-small-3.1-24b-instruct",
"label": "Mistral Small 3.1 24B",
"alias": [],
"context": 128000,
"caps": ["chat", "tools", "long-context"],
"priority": 25
},
{
"id": "@cf/zhipu/glm-4.7-flash",
"label": "GLM 4.7 Flash",
"alias": [],
"context": 131072,
"caps": ["chat", "tools", "reasoning", "long-context"],
"priority": 25
},
{
"id": "@cf/baai/bge-large-en-v1.5",
"label": "BGE Large EN v1.5",
"alias": ["embed-large"],
"context": 512,
"caps": ["embed"],
"priority": 20,
"credits": { "perMTokIn": 18582 }
},
{
"id": "@cf/baai/bge-small-en-v1.5",
"label": "BGE Small EN v1.5",
"alias": [],
"context": 512,
"caps": ["embed"],
"priority": 30,
"credits": { "perMTokIn": 1841 }
}
]
}
+17
View File
@@ -0,0 +1,17 @@
{
"$schema": "./_schema.json",
"id": "github-models",
"label": "GitHub Models",
"docs": "https://docs.github.com/en/github-models",
"console": "https://github.com/settings/tokens",
"priority": 45,
"baseUrl": "https://models.github.ai/inference",
"auth": { "type": "bearer", "envKeys": ["GITHUB_MODELS_TOKEN", "GITHUB_TOKEN"] },
"limits": { "rpm": null, "rpd": null },
"limitsSource": "unverified",
"verifiedOn": "2026-08-16",
"modelsEndpoint": "/models",
"modelsPath": "[].id",
"notes": "Free for anyone with a GitHub account; needs a PAT with the models:read scope. Limits are tiered by account and not published as flat numbers, hence nulls — calibration learns them. Note modelsPath is [].id: the catalog is a bare array, not an OpenAI {data:[...]} envelope.",
"models": []
}
+20
View File
@@ -0,0 +1,20 @@
{
"$schema": "./_schema.json",
"id": "google-ai",
"label": "Google AI Studio",
"docs": "https://ai.google.dev/gemini-api/docs/openai",
"console": "https://aistudio.google.com/apikey",
"priority": 25,
"baseUrl": "https://generativelanguage.googleapis.com/v1beta/openai",
"auth": { "type": "bearer", "envKeys": ["GOOGLE_AI_API_KEY", "GEMINI_API_KEY"] },
"limits": { "rpm": 15, "rpd": 1500 },
"limitsSource": "docs",
"verifiedOn": "2026-08-16",
"modelsEndpoint": "/models",
"modelsPath": "data[].id",
"notes": "The only free tier with native multimodal input across text, image, audio and video in one call. IMPORTANT: the free tier is not available in the EU, UK or Switzerland. Limits shown are for Flash; Pro models are far tighter (~2 rpm).",
"models": [
{ "id": "gemini-2.5-flash", "label": "Gemini 2.5 Flash", "alias": ["fast", "vision"], "context": 1048576, "caps": ["chat", "tools", "json", "vision", "long-context"], "priority": 10 },
{ "id": "text-embedding-004", "label": "Text Embedding 004", "alias": ["embed"], "context": 2048, "caps": ["embed"], "priority": 20 }
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"$schema": "./_schema.json",
"id": "groq",
"label": "Groq",
"docs": "https://console.groq.com/docs/models",
"console": "https://console.groq.com/keys",
"priority": 15,
"baseUrl": "https://api.groq.com/openai/v1",
"auth": { "type": "bearer", "envKeys": ["GROQ_API_KEY", "GROQ_API_KEY_2"] },
"limits": { "rpm": 30, "rpd": 14400, "tpm": 6000 },
"limitsSource": "docs",
"verifiedOn": "2026-08-16",
"modelsEndpoint": "/models",
"modelsPath": "data[].id",
"notes": "LPU inference, very fast. Free limits vary per model — the numbers here are the commonly published floor, and the guard layer replaces them with per-key values read from x-ratelimit-* response headers. Run scripts/verify-provider.mjs groq to populate the real model list.",
"models": [
{ "id": "llama-3.3-70b-versatile", "label": "Llama 3.3 70B", "alias": ["fast", "big"], "context": 131072, "caps": ["chat", "tools", "json", "long-context"], "priority": 10 },
{ "id": "llama-3.1-8b-instant", "label": "Llama 3.1 8B Instant", "alias": ["cheap"], "context": 131072, "caps": ["chat", "tools", "json"], "priority": 20 }
]
}
+17
View File
@@ -0,0 +1,17 @@
{
"$schema": "./_schema.json",
"id": "huggingface",
"label": "Hugging Face Router",
"docs": "https://huggingface.co/docs/inference-providers",
"console": "https://huggingface.co/settings/tokens",
"priority": 50,
"baseUrl": "https://router.huggingface.co/v1",
"auth": { "type": "bearer", "envKeys": ["HF_TOKEN", "HUGGINGFACE_API_KEY"] },
"limits": { "rpm": null },
"limitsSource": "unverified",
"verifiedOn": "2026-08-16",
"modelsEndpoint": "/models",
"modelsPath": "data[].id",
"notes": "Routes to whichever inference provider currently serves a model, so availability moves around. Free monthly credits are small; treat this as overflow rather than a primary. Model ids are vendor/model, handled by the first-slash pin rule.",
"models": []
}
+398
View File
@@ -0,0 +1,398 @@
{
"$schema": "./_schema.json",
"id": "mistral",
"label": "Mistral AI",
"docs": "https://docs.mistral.ai/getting-started/models/models_overview/",
"console": "https://console.mistral.ai/api-keys",
"enabled": true,
"priority": 10,
"adapter": "openai",
"baseUrl": "https://api.mistral.ai/v1",
"auth": {
"type": "bearer",
"envKeys": [
"MISTRAL_API_KEY",
"MISTRAL_API_KEY_2"
]
},
"limits": {
"tpmo": 1000000000,
"tpm": 25000
},
"limitsSource": "observed",
"verifiedOn": "2026-08-17",
"modelsEndpoint": "/models",
"modelsPath": "data[].id",
"dropParams": [],
"headers": {},
"notes": "Free 'Experiment' plan: phone verification required, no credit card. Model ids are -latest aliases confirmed against a live GET /models on 2026-08-17 — deliberately NOT the dated ids from the docs overview page, several of which return 'Invalid model' on the API. Prefer -latest: it survives Mistral retiring a dated version. tpm was observed from x-ratelimit headers on /chat/completions (Mistral sends none on /models). Regenerate with: npm run verify-provider mistral -- --validate --apply",
"models": [
{
"id": "mistral-small-latest",
"label": "Mistral Small",
"alias": [
"fast",
"chat"
],
"context": 128000,
"caps": [
"chat",
"tools",
"json",
"code"
],
"priority": 10
},
{
"id": "mistral-medium-latest",
"label": "Mistral Medium",
"alias": [
"smart"
],
"context": 128000,
"caps": [
"chat",
"tools",
"json",
"code",
"vision"
],
"priority": 15
},
{
"id": "mistral-large-latest",
"label": "Mistral Large",
"alias": [
"big"
],
"context": 128000,
"caps": [
"chat",
"tools",
"json",
"code",
"vision"
],
"priority": 20
},
{
"id": "ministral-8b-latest",
"label": "Ministral 8B",
"alias": [
"cheap"
],
"context": 128000,
"caps": [
"chat",
"tools"
],
"priority": 30
},
{
"id": "ministral-3b-latest",
"label": "Ministral 3B",
"alias": [],
"context": 128000,
"caps": [
"chat",
"tools"
],
"priority": 40
},
{
"id": "magistral-small-latest",
"label": "Magistral Small (reasoning)",
"alias": [
"reason"
],
"context": 128000,
"caps": [
"chat",
"reasoning"
],
"priority": 25
},
{
"id": "codestral-latest",
"label": "Codestral",
"alias": [
"code"
],
"context": 256000,
"caps": [
"chat",
"code"
],
"priority": 20
},
{
"id": "devstral-medium-latest",
"label": "Devstral Medium",
"alias": [],
"context": 128000,
"caps": [
"chat",
"tools",
"code"
],
"priority": 35
},
{
"id": "mistral-embed",
"label": "Mistral Embed",
"alias": [
"embed"
],
"context": 8000,
"caps": [
"embed"
],
"priority": 10
},
{
"id": "mistral-medium-2505",
"label": "mistral-medium-2505",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "mistral-medium-2508",
"label": "mistral-medium-2508",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "codestral-2508",
"label": "codestral-2508",
"alias": [],
"caps": [
"chat",
"code"
],
"context": null,
"priority": 50
},
{
"id": "mistral-code-latest",
"label": "mistral-code-latest",
"alias": [],
"caps": [
"chat",
"code"
],
"context": null,
"priority": 50
},
{
"id": "mistral-code-fim-latest",
"label": "mistral-code-fim-latest",
"alias": [],
"caps": [
"chat",
"code"
],
"context": null,
"priority": 50
},
{
"id": "devstral-2512",
"label": "devstral-2512",
"alias": [],
"caps": [
"chat",
"code"
],
"context": null,
"priority": 50
},
{
"id": "devstral-latest",
"label": "devstral-latest",
"alias": [],
"caps": [
"chat",
"code"
],
"context": null,
"priority": 50
},
{
"id": "mistral-small-2603",
"label": "mistral-small-2603",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "mistral-vibe-cli-fast",
"label": "mistral-vibe-cli-fast",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "mistral-large-2512",
"label": "mistral-large-2512",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "ministral-3b-2512",
"label": "ministral-3b-2512",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "ministral-8b-2512",
"label": "ministral-8b-2512",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "ministral-14b-2512",
"label": "ministral-14b-2512",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "ministral-14b-latest",
"label": "ministral-14b-latest",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "mistral-medium",
"label": "mistral-medium",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "mistral-medium-3-5",
"label": "mistral-medium-3-5",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "mistral-medium-3.5",
"label": "mistral-medium-3.5",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "mistral-medium-3",
"label": "mistral-medium-3",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "mistral-medium-2604",
"label": "mistral-medium-2604",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "mistral-vibe-cli-latest",
"label": "mistral-vibe-cli-latest",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "mistral-vibe-cli-with-tools",
"label": "mistral-vibe-cli-with-tools",
"alias": [],
"caps": [
"chat"
],
"context": null,
"priority": 50
},
{
"id": "mistral-embed-2312",
"label": "mistral-embed-2312",
"alias": [],
"caps": [
"embed"
],
"context": null,
"priority": 50
},
{
"id": "codestral-embed",
"label": "codestral-embed",
"alias": [],
"caps": [
"embed"
],
"context": null,
"priority": 50
},
{
"id": "codestral-embed-2505",
"label": "codestral-embed-2505",
"alias": [],
"caps": [
"embed"
],
"context": null,
"priority": 50
}
]
}
+17
View File
@@ -0,0 +1,17 @@
{
"$schema": "./_schema.json",
"id": "nvidia-nim",
"label": "NVIDIA NIM",
"docs": "https://docs.api.nvidia.com/nim/",
"console": "https://build.nvidia.com/",
"priority": 50,
"baseUrl": "https://integrate.api.nvidia.com/v1",
"auth": { "type": "bearer", "envKeys": ["NVIDIA_API_KEY"] },
"limits": { "rpm": null },
"limitsSource": "unverified",
"verifiedOn": "2026-08-16",
"modelsEndpoint": "/models",
"modelsPath": "data[].id",
"notes": "Free developer credits rather than a perpetual free tier — it runs out. Model ids are vendor/model.",
"models": []
}
+17
View File
@@ -0,0 +1,17 @@
{
"$schema": "./_schema.json",
"id": "ollama",
"label": "Ollama (local)",
"docs": "https://github.com/ollama/ollama/blob/main/docs/openai.md",
"console": "https://ollama.com/library",
"priority": 90,
"baseUrl": "${OLLAMA_HOST}/v1",
"auth": { "type": "none", "envKeys": [] },
"limits": {},
"limitsSource": "docs",
"verifiedOn": "2026-08-16",
"modelsEndpoint": "/models",
"modelsPath": "data[].id",
"notes": "Your own machine, so there is no quota to meter and no key to hold — the point of auth.type: none. Set OLLAMA_HOST=http://127.0.0.1:11434 to enable. Priority 90 keeps it as the last resort, i.e. the thing that still answers when every hosted free tier is exhausted. Leave models empty; discovery lists whatever you have pulled.",
"models": []
}
+20
View File
@@ -0,0 +1,20 @@
{
"$schema": "./_schema.json",
"id": "openrouter",
"label": "OpenRouter (free models)",
"docs": "https://openrouter.ai/docs",
"console": "https://openrouter.ai/keys",
"priority": 40,
"baseUrl": "https://openrouter.ai/api/v1",
"auth": { "type": "bearer", "envKeys": ["OPENROUTER_API_KEY"] },
"limits": { "rpm": 20, "rpd": 50 },
"limitsSource": "unverified",
"verifiedOn": "2026-08-16",
"modelsEndpoint": "/models",
"modelsPath": "data[].id",
"headers": { "HTTP-Referer": "https://github.com/freeway", "X-Title": "Freeway" },
"notes": "Only :free-suffixed models cost nothing. Free daily request caps depend on account credit history, so limits here are unverified on purpose. Model ids contain a slash (vendor/model), which the first-slash pin rule handles.",
"models": [
{ "id": "meta-llama/llama-3.3-70b-instruct:free", "label": "Llama 3.3 70B (free)", "alias": ["big"], "context": 65536, "caps": ["chat", "tools"], "priority": 10 }
]
}
+17
View File
@@ -0,0 +1,17 @@
{
"$schema": "./_schema.json",
"id": "scaleway",
"label": "Scaleway Generative APIs",
"docs": "https://www.scaleway.com/en/docs/generative-apis/",
"console": "https://console.scaleway.com/iam/api-keys",
"priority": 55,
"baseUrl": "https://api.scaleway.ai/v1",
"auth": { "type": "bearer", "envKeys": ["SCALEWAY_API_KEY"] },
"limits": { "rpm": null, "tpm": null },
"limitsSource": "unverified",
"verifiedOn": "2026-08-16",
"modelsEndpoint": "/models",
"modelsPath": "data[].id",
"notes": "EU-hosted (Paris), useful when data residency matters. Free tier exists but the published numbers move; nulls plus calibration are more honest than a snapshot.",
"models": []
}
+17
View File
@@ -0,0 +1,17 @@
{
"$schema": "./_schema.json",
"id": "together",
"label": "Together AI",
"docs": "https://docs.together.ai/docs/inference-models",
"console": "https://api.together.xyz/settings/api-keys",
"priority": 45,
"baseUrl": "https://api.together.xyz/v1",
"auth": { "type": "bearer", "envKeys": ["TOGETHER_API_KEY"] },
"limits": { "rpm": 60 },
"limitsSource": "unverified",
"verifiedOn": "2026-08-16",
"modelsEndpoint": "/models",
"modelsPath": "[].id",
"notes": "Free tier is limited to models with a -Free suffix; everything else bills. Leaving models empty on purpose so discovery reports what your key can actually reach rather than guessing at a moving list.",
"models": []
}