Free LLM Router Documentation
Get Started
Use free OpenRouter models without managing availability or fallbacks.
Set Up OpenRouter
Create a dedicated OpenRouter API key for free-model requests.
Get Your API Key
Sign in with GitHub to create a Free LLM Router key.
Copy free-llm-router.ts
The helper fetches model IDs, caches them, and reports results.
const API = 'https://freellmrouter.com/api/v1';
const API_KEY = typeof process !== 'undefined' ? process.env.FREE_LLM_ROUTER_API_KEY : undefined;
type UseCase = 'chat' | 'vision' | 'tools' | 'longContext' | 'reasoning';
type Sort = 'contextLength' | 'maxOutput' | 'capable' | 'leastIssues' | 'newest';
type CacheMode = 'default' | 'no-store';
type TimeRange = '15m' | '30m' | '1h' | '6h' | '24h' | '3d' | '7d' | '30d' | 'all';
const CACHE_TTL = 15 * 60 * 1000;
interface GetModelIdsResult {
ids: string[];
requestId: string;
}
const cache = new Map<string, { data: GetModelIdsResult; timestamp: number }>();
export async function getModelIds(
useCase?: UseCase[],
sort?: Sort,
topN?: number,
options?: {
cache?: CacheMode;
maxErrorRate?: number;
timeRange?: TimeRange;
myReports?: boolean;
}
): Promise<GetModelIdsResult> {
// Keep cache keys stable regardless of useCase order.
const normalizedUseCase = useCase ? [...useCase].sort() : undefined;
const cacheKey = JSON.stringify({
useCase: normalizedUseCase,
sort,
topN,
maxErrorRate: options?.maxErrorRate,
timeRange: options?.timeRange,
myReports: options?.myReports,
});
const cached = cache.get(cacheKey);
const cacheMode = options?.cache ?? 'default';
if (cacheMode === 'default' && cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
try {
const params = new URLSearchParams();
if (normalizedUseCase) params.set('useCase', normalizedUseCase.join(','));
if (sort) params.set('sort', sort);
if (topN) params.set('topN', String(topN));
if (options?.maxErrorRate !== undefined) {
params.set('maxErrorRate', String(options.maxErrorRate));
}
if (options?.timeRange) {
params.set('timeRange', options.timeRange);
}
if (options?.myReports) {
params.set('myReports', 'true');
}
const { ids, requestId } = await fetch(`${API}/models/ids?${params}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
}).then((r) => r.json());
const result: GetModelIdsResult = { ids, requestId };
cache.set(cacheKey, { data: result, timestamp: Date.now() });
return result;
} catch (error) {
// Prefer stale cache over hard failure when available.
if (cached) {
if (process.env.NODE_ENV !== 'production') {
console.warn('API request failed, using stale cached data', error);
}
return cached.data;
}
throw error;
}
}
export function reportIssue(
modelId: string,
issue: 'error' | 'rate_limited' | 'unavailable',
requestId?: string,
details?: string
) {
fetch(`${API}/models/feedback`, {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ modelId, issue, requestId, details }),
}).catch(() => {});
}
export function reportSuccess(modelId: string, requestId?: string, details?: string) {
fetch(`${API}/models/feedback`, {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ modelId, success: true, requestId, details }),
}).catch(() => {});
}
export function issueFromStatus(status: number): 'rate_limited' | 'unavailable' | 'error' {
if (status === 429) return 'rate_limited';
if (status === 503) return 'unavailable';
return 'error';
}
Use It
Use saved defaults or override them for one request.
import { getModelIds, reportSuccess, reportIssue, issueFromStatus } from './free-llm-router';
try {
// getModelIds() with no params applies your saved configured params automatically
const { ids: freeModels, requestId } = await getModelIds()
for (const id of freeModels) {
try {
const res = await client.chat.completions.create({ model: id, messages });
reportSuccess(id, requestId);
return res;
} catch (e) {
const status = e.status || e.response?.status;
reportIssue(id, issueFromStatus(status), requestId, e.message);
}
}
} catch {
}
throw new Error('All models failed');Parameter Configuration
Save model filters to an API key or override them per request.
Configure Parameters
0 free models
Key Defaults
Calls without parameters use the selected key's saved configuration.
const { ids, requestId } = await getModelIds()Request Overrides
Passed parameters apply to that request only.
const { ids, requestId } = await getModelIds([], 'contextLength', 5)Code Examples
One-off API Call
Simple single prompt completion - perfect for scripts, CLI tools, or serverless functions.
import { getModelIds, reportSuccess, reportIssue, issueFromStatus } from './free-llm-router';
const prompt = 'Summarize this article in 3 bullet points: ...';
try {
// fetch ranked model IDs + requestId
// Get top 3 models with both chat and vision capabilities
// SDK has built-in 15-min cache, so this won't hit the API on every call
const { ids: models, requestId } = await getModelIds(['chat', 'vision'], 'capable', 3);
// Try each model until one succeeds
for (const id of models) {
try {
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: id,
messages: [{ role: 'user', content: prompt }],
}),
});
if (!res.ok) {
// Report the right issue type - free, doesn't use quota
reportIssue(id, issueFromStatus(res.status), requestId, `HTTP ${res.status}`);
continue;
}
const data = await res.json();
console.log(data.choices[0].message.content);
// submit success feedback
// Report success - helps other users know this model works!
reportSuccess(id, requestId);
break; // Success - exit loop
} catch (e) {
const status = e.status || e.response?.status;
// submit issue feedback
reportIssue(id, issueFromStatus(status), requestId, e.message); // Free - doesn't use quota
}
}
} catch {
// API unavailable - handle gracefully
console.error('Failed to fetch models');
}Chatbot
Multi-turn conversation with message history - ideal for chat interfaces.
import { getModelIds, reportSuccess, reportIssue, issueFromStatus } from './free-llm-router';
import OpenAI from 'openai';
// OpenAI SDK works with OpenRouter's API
const client = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY,
});
// Store conversation history for multi-turn chat
const messages: OpenAI.ChatCompletionMessageParam[] = [];
async function chat(userMessage: string) {
messages.push({ role: 'user', content: userMessage });
try {
// fetch ranked model IDs + requestId
// SDK has built-in 15-min cache, so this won't hit the API on every call
const { ids: models, requestId } = await getModelIds(['chat'], 'capable', 5);
for (const id of models) {
try {
const res = await client.chat.completions.create({
model: id,
messages, // Include full history
});
const reply = res.choices[0].message.content;
messages.push({ role: 'assistant', content: reply });
// submit success feedback
// Report success - helps other users know this model works!
reportSuccess(id, requestId);
return reply;
} catch (e) {
// submit issue feedback
// Report with correct issue type - free, doesn't use quota
reportIssue(id, issueFromStatus(e.status), requestId, e.message);
}
}
} catch {
// API unavailable
}
throw new Error('All models failed');
}Tool Calling
Let the model call functions - for agents, data fetching, or structured outputs.
import { getModelIds, reportSuccess, reportIssue, issueFromStatus } from './free-llm-router';
import { createOpenAI } from '@ai-sdk/openai';
import { generateText, tool } from 'ai';
import { z } from 'zod';
// Vercel AI SDK with OpenRouter
const openrouter = createOpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY,
});
// Define tools with Zod schemas
const tools = {
getWeather: tool({
description: 'Get current weather for a location',
parameters: z.object({ location: z.string() }),
execute: async ({ location }) => `72°F and sunny in ${location}`,
}),
};
async function askWithTools(prompt: string) {
try {
// Filter for models that support tool calling
// fetch ranked model IDs + requestId
// SDK has built-in 15-min cache, so this won't hit the API on every call
const { ids: models, requestId } = await getModelIds(['tools'], 'capable', 3);
for (const id of models) {
try {
const { text, toolCalls } = await generateText({
model: openrouter(id),
prompt,
tools,
});
// submit success feedback
reportSuccess(id, requestId); // Helps improve health metrics
return { text, toolCalls };
} catch (e) {
// submit issue feedback
// Report with correct issue type - free, doesn't use quota
reportIssue(id, issueFromStatus(e.status), requestId, e.message);
}
}
} catch {
// API unavailable
}
throw new Error('All models failed');
}API Reference
Endpoints for model selection and feedback.
/api/v1/models/ids
Returns model IDs for routing.
See Query Parameters.
Response
| Field | Type | Description |
|---|---|---|
| ids | string[] | Array of model IDs |
| count | number | Number of IDs returned |
Errors
500- Server error
Cache-Control: private, max-age=60 - Responses are cached for 60 seconds at the HTTP layer and 15 minutes in the SDK.
Request
Required to send requests.
curl https://freellmrouter.com/api/v1/models/ids?sort=contextLength&topN=5 \
-H "Authorization: Bearer YOUR_API_KEY" \Response
{
"ids": [
"google/gemini-2.0-flash-exp:free",
"meta-llama/llama-3.3-70b-instruct:free",
"deepseek/deepseek-chat:free"
],
"count": 15
}/api/v1/models/full
Returns models with metadata and health data.
Query Parameters
Same parameters as /models/ids.
Response
| Field | Type | Description |
|---|---|---|
| models | Model[] | Full model objects with all metadata |
| feedbackCounts | object | Per-model feedback: issue counts, success count, and error rate (percentage). Error rate shows % of failed requests. |
| lastUpdated | string | ISO 8601 timestamp of last sync |
| useCases | string[] | Applied use case values |
| sort | string | Applied sort value |
| count | number | Total number of models returned |
Cache-Control: private, max-age=60 - Responses are cached for 60 seconds at the HTTP layer and 15 minutes in the SDK.
Request
Required to send requests.
curl https://freellmrouter.com/api/v1/models/full?sort=contextLength&topN=5 \
-H "Authorization: Bearer YOUR_API_KEY" \Response
{
"models": [
{
"id": "google/gemini-2.0-flash-exp:free",
"name": "Gemini 2.0 Flash",
"contextLength": 1000000,
"maxCompletionTokens": 8192,
"description": "...",
"inputModalities": ["text", "image"],
"outputModalities": ["text"],
"supportedParameters": ["tools", "reasoning"]
}
],
"feedbackCounts": { ... },
"lastUpdated": "2024-12-29T10:00:00Z",
"filters": ["vision"],
"sort": "contextLength",
"count": 15
}/api/v1/models/feedback
Reports a successful or failed model request. Does not count towards your rate limit.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| modelId | string | Yes | The model ID to report |
| success | boolean | No | Set to true to report successful request. If omitted, reports an issue (requires issue field). |
| issue | string | Yes | Required if success is false/omitted. One of: rate_limited, unavailable, error |
| details | string | No | Optional description of the issue |
| dryRun | boolean | No | If true, validates request but doesn't save (for testing) |
Response
| Field | Type | Description |
|---|---|---|
| received | boolean | Whether feedback was recorded |
Errors
400- Missing modelId or invalid issue type500- Server error
Request
Required to send requests.
curl -X POST https://freellmrouter.com/api/v1/models/feedback \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"modelId": "google/gemini-2.0-flash-exp:free",
"success": true,
"dryRun": true
}'Response
{ "received": true }Query Parameters
All parameters are optional and can be combined.
useCase
Select models by use case. Pass one or more as a comma-separated list: ?useCase=vision,tools
| Value | Description |
|---|---|
| chat | Text-to-text models optimized for conversation |
| vision | Models that accept image inputs |
| tools | Models that support function/tool calling |
| longContext | Models with 100k+ token context windows |
| reasoning | Models with advanced reasoning capabilities (e.g., o1, QwQ, DeepSeek R1) |
sort
Control the order models are returned. This determines fallback priority when iterating through the list. Example: ?sort=contextLength
| Value | Label | Description |
|---|---|---|
| contextLength | Context Length | Largest context window first - best for long documents |
| maxOutput | Max Output | Highest output token limit first - best for long-form generation |
| capable | Most Capable | Most supported features first - good default |
| leastIssues | Least Reported Issues | Fewest user-reported issues first - best for stability |
| newest | Newest First | Most recently added models first - best for trying new models |
topN
Return only the top N models based on sort order. Range: 1-100. Default: unlimited. Example: ?topN=10
maxErrorRate
Exclude models with error rate above this percentage (0-100). Error rate = errors / (errors + successes). Example: ?maxErrorRate=20 excludes models with more than 20% error rate.
timeRange
Time window for calculating error rates. Options: 15m, 30m, 1h, 6h, 24h, 3d, 7d, 30d, all. Default: 3d.
myReports
When set to true, calculate error rates from only your own reported issues instead of all community reports. Requires API key authentication. Default: false. Example: ?myReports=true
Frequently Asked Questions
Can I build an MVP with free LLM models?
Yes. Free LLM Router is designed for demos, prototypes, and MVPs that need to validate an idea without paying model API costs. It provides currently available free OpenRouter model IDs through one API.
Why use Free LLM Router with OpenRouter?
OpenRouter provides access to many free LLM models, but an individual free model can be rate limited, reach capacity, or disappear without notice. Free LLM Router maintains a live-updated, ordered list so you do not have to track availability or maintain the fallback list yourself.
How does Free LLM Router work with OpenRouter?
Set your use case and sorting preferences, fetch the ordered model IDs, and send them to OpenRouter. OpenRouter tries the models in order until one responds, while the helper caches the list and reports successful and failed requests back to the health dataset.
How are free models selected?
Models can be filtered by use case, sorted by capability or health, limited to a top result count, and filtered by reported error rate. Defaults can be saved per API key or overridden for one request. Model exclusions are saved per key and applied automatically.
How is model availability tracked?
Availability comes from OpenRouter model syncs, while health data comes from reported successes and issues. Together they help identify free models that are currently available and working reliably.
How do I avoid accidental OpenRouter charges?
Create a separate OpenRouter API key for free-model requests and set a small credit limit on your OpenRouter account. This protects you if a paid model is selected accidentally.
What is the Free LLM Router API rate limit?
All Free LLM Router API keys for a user share a limit of 200 requests per 24 hours. The helper caches model lists, so most applications do not need to request the list for every model call.