Free LLM Router Documentation

Get Started

Use free OpenRouter models without managing availability or fallbacks.

1

Set Up OpenRouter

Create a dedicated OpenRouter API key for free-model requests.

2

Get Your API Key

Sign in with GitHub to create a Free LLM Router key.

3

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';
}
4

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

Use Case
Sort
Top N
Health Filter
Hide Models
Live

0 free models

Loading...

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.

GET

/api/v1/models/ids

Returns model IDs for routing.

See Query Parameters.

Response

FieldTypeDescription
idsstring[]Array of model IDs
countnumberNumber 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

API Key

Required to send requests.

Use Case
Sort
Top N
Health Filter
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
}
GET

/api/v1/models/full

Returns models with metadata and health data.

Query Parameters

Same parameters as /models/ids.

Response

FieldTypeDescription
modelsModel[]Full model objects with all metadata
feedbackCountsobjectPer-model feedback: issue counts, success count, and error rate (percentage). Error rate shows % of failed requests.
lastUpdatedstringISO 8601 timestamp of last sync
useCasesstring[]Applied use case values
sortstringApplied sort value
countnumberTotal 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

API Key

Required to send requests.

Use Case
Sort
Top N
Health Filter
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
}
POST

/api/v1/models/feedback

Reports a successful or failed model request. Does not count towards your rate limit.

Request Body

ParameterTypeRequiredDescription
modelIdstringYesThe model ID to report
successbooleanNoSet to true to report successful request. If omitted, reports an issue (requires issue field).
issuestringYesRequired if success is false/omitted. One of: rate_limited, unavailable, error
detailsstringNoOptional description of the issue
dryRunbooleanNoIf true, validates request but doesn't save (for testing)

Response

FieldTypeDescription
receivedbooleanWhether feedback was recorded

Errors

  • 400 - Missing modelId or invalid issue type
  • 500 - Server error

Request

API Key

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

ValueDescription
chatText-to-text models optimized for conversation
visionModels that accept image inputs
toolsModels that support function/tool calling
longContextModels with 100k+ token context windows
reasoningModels 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

ValueLabelDescription
contextLengthContext LengthLargest context window first - best for long documents
maxOutputMax OutputHighest output token limit first - best for long-form generation
capableMost CapableMost supported features first - good default
leastIssuesLeast Reported IssuesFewest user-reported issues first - best for stability
newestNewest FirstMost 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.

We are not OpenRouter. Data is from community reports.