Freerouter

Reference

Authentication headers, error codes, environment variables, deployment, and configuration reference for the FreeRouter API. Uses only free providers - $0 cost.

Authentication

The API has no built-in API key. It is a stateless proxy - downstream provider authentication happens via per-provider HTTP headers.

Header mapping

Each provider uses its own header for the API key:

ProviderHeader
GroqX-Groq-Key
GoogleX-Google-Key
OpenRouterX-Openrouter-Key
CloudflareX-Cloudflare-Key
NVIDIA NIMX-Nvidia-Key
CerebrasX-Cerebras-Key
Together AIX-Together-Key
Fireworks AIX-Fireworks-Key
Mistral AIX-Mistral-Key
SambaNovaX-Sambanova-Key
DeepSeekX-Deepseek-Key
DeepInfraX-Deepinfra-Key
CohereX-Cohere-Key

How it works

  1. Server reads provider headers from the incoming request
  2. Builds a FreeRouterKeys object with only the providers present in headers
  3. Passes keys to freerouter.languageModel(alias, keys)
  4. Resolution automatically excludes providers with no key

Security

Key protection

  • Keys are never logged - the request logger sanitizes all headers and paths
  • Keys are never stored server-side
  • Error messages redact key patterns matching known formats
  • CORS allows all 13 provider key headers through

Error Responses

All errors are returned in OpenAI-compatible format.

Error format

Error response body
{
  "error": {
    "message": "All providers failed: groq: rate limit exceeded; google: Internal server error",
    "type": "freerouter_all_providers_failed",
    "code": 502
  }
}

Status codes

HTTP StatusError TypeCondition
400invalid_request_errorInvalid request body (Zod validation failure)
429rate_limit_errorIP-based rate limit exceeded
502freerouter_all_providers_failedAll candidate providers exhausted
502freerouter_provider_errorSingle provider error
500server_errorUnexpected internal error

400 - Invalid request

400 Bad Request
{
  "error": {
    "message": "Validation failed: messages: Expected array, received string",
    "type": "invalid_request_error",
    "code": 400
  }
}

429 - Rate limited

Default: 100 requests per 60 seconds per IP. Response includes Retry-After header.

429 Too Many Requests
{
  "error": {
    "message": "Rate limit exceeded. Retry after 42 seconds",
    "type": "rate_limit_error",
    "code": 429
  }
}
Response headers
Retry-After: 42

502 - Provider errors

502 Bad Gateway
{
  "error": {
    "message": "All providers failed: groq: rate limit exceeded; google: Internal server error",
    "type": "freerouter_all_providers_failed",
    "code": 502
  }
}

Key redaction

Known key patterns are replaced with [REDACTED] in error messages:

PatternProvider
gsk_...Groq
sk-or-...OpenRouter
AIza...Google
nvapi-...NVIDIA NIM
CF...Cloudflare
ghp_... / github_pat_...GitHub (general precaution)

Client-side error handling

Error handling example
import OpenAI from "openai"

const client = new OpenAI({
  baseURL: "http://localhost:3000/v1",
  apiKey: "unused",
})

async function retryOnRateLimit(model: string, messages: any[], maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await client.chat.completions.create(
        { model, messages },
        { headers: { "X-Groq-Key": process.env.GROQ_API_KEY } }
      )
    } catch (err: any) {
      if (err.status === 429) {
        const retryAfter = Number(err.headers?.get("Retry-After") ?? 5)
        console.log(`Rate limited. Retrying after ${retryAfter}s...`)
        await new Promise((r) => setTimeout(r, retryAfter * 1000))
        continue
      }
      if (err.status === 502) {
        throw new Error("All providers are down. Try again later.")
      }
      throw err
    }
  }
  throw new Error("Max retries exceeded")
}

Deployment

Environment variables

Prop

Type

Running the server

bun run apps/api/src/index.ts

Graceful shutdown

The server handles SIGTERM and SIGINT for clean teardown:

^C received. Shutting down gracefully...

Rate limiting

IP-based, in-memory sliding window.

SettingDefaultNotes
RATE_LIMIT_MAX100Max requests per window per IP
RATE_LIMIT_WINDOW_MS60000Window in ms
  • IP determined by x-forwarded-for header → x-real-ip → remote address
  • 429 responses include Retry-After header
  • Stale buckets cleaned every 5 minutes

CORS

Enabled on all /v1/* routes. Allow headers include all 13 provider key headers plus Retry-After.

CORS_ORIGINS env var controls allowed origins (default: *).

Logging

Per-request logging with automatic key sanitization:

Log output
GET /v1/models 200 42ms
POST /v1/chat/completions 200 1234ms

Sanitization

Any 8+ character alphanumeric token in log lines is automatically replaced with [REDACTED]. This catches API keys, tokens, and other sensitive strings.

Testing

bun test --filter @freerouter/api

Test coverage includes:

AreaWhat's tested
HealthGET /health returns status and providers
ModelsGET /v1/models returns aliases + concrete models
Chat completionsNon-streaming, streaming, alias, pinned model
AuthenticationHeader extraction, partial keys
Rate limiting429 response, retry-after header
Error mappingAll error codes, key sanitization
OpenAI SDK compatibilityEnd-to-end with official SDK

On this page