API Reference

FreeRouter exposes an OpenAI-compatible REST API at https://api.freerouter.com/v1. Every endpoint mirrors the shape your key speaks — OpenAI by default.

Base URL

https://api.freerouter.com/v1

All endpoints below are relative to this base. FreeRouter serves HTTPS only. Agents and tooling can consume the same surface as machine-readable OpenAPI 3.0.

Authentication

Authenticate with your FreeRouter key as a Bearer token. Never ship it in client-side code — treat it like any server secret.

Authorization: Bearer fr_live_your_key_here

The headers your SDK already sends work too, so you only change the key and base URL: Authorization: Bearer (OpenAI, OpenRouter), x-api-key (Anthropic SDK, Claude Code's ANTHROPIC_API_KEY), or x-goog-api-key (Gemini). On :generateContent the key may also ride as ?key=, but a header keeps it out of access logs.

Each key carries its own API shape and routing rule. Requests with a missing or invalid key return 401 Unauthorized. A key can also be limited to specific client addresses; see IP allowlists under Security.

POST /v1/chat/completions

The primary inference endpoint. It accepts the standard OpenAI chat completion request body and returns a standard chat completion object (or an SSE stream when stream is true).

Request body

FieldTypeNotes
modelstringRequired. A provider/model identifier, e.g. openai/gpt-4o-mini.
messagesarrayRequired. Chat messages with role and content.
streambooleanOptional. Stream partial deltas as server-sent events.
temperaturenumberOptional. Sampling temperature, passed through to the gateway.
max_tokensintegerOptional. Upper bound on generated tokens.
toolsarrayOptional. Tool/function definitions, passed through unchanged.

Any other OpenAI-compatible fields are forwarded to the selected gateway as-is.

Example request

curl https://api.freerouter.com/v1/chat/completions \
  -H "Authorization: Bearer $FREEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o-mini",
    "messages": [{ "role": "user", "content": "Ping" }],
    "temperature": 0.2
  }'

Response headers

Beyond the standard headers, FreeRouter adds routing metadata so you can observe behavior:

HeaderDescription
X-FreeRouter-ProviderThe gateway that served the request, e.g. openrouter.
X-FreeRouter-AttemptsNumber of gateways tried, including failover retries.
X-FreeRouter-Request-IdOpaque ID for support and log correlation. On /v1/messages the same id is also sent as request-id, the header the Anthropic SDKs read.
X-RateLimit-*, Retry-AfterThe gateway's own rate-limit headers, passed through on every response that reached a gateway (buffered, streamed or translated), under whatever names the gateway uses. Nothing else of the gateway's headers is forwarded.
X-FreeRouter-FailoverOnly when a failover happened: each skipped attempt as gateway:reason. See Why did a request fail over?
X-FreeRouter-Key-Grace-UntilOnly when the request used a rotated key's previous secret inside its grace window: when that secret stops working (ISO 8601). Switch to the new secret before then.
X-FreeRouter-DecisionerOnly on keys with a decisioner: what Jev did, as ;-separated key:value pairs. A pick reads pick:<gateway>;model:<id>;via:<how>;choice:<option>;conf:<0–1>;p:<probability>;obj:<objective> — model only when Jev overrode the inbound model, via only when the pick was made without a Jev call (code: the cost answer was unique) or by a secondary question (complexity, needs_tools, remap). A skip reads skipped:<reason>;obj:… — e.g. skipped:no_facts or skipped:single_target. Absent when the key has no decisioner. Capped at 500 characters.
Correlating with logs Quote the X-FreeRouter-Request-Id when asking for help — FreeRouter logs per-request metadata (key, workspace, gateway, model, status, latency, stage timings, attempts, the attempt path, and that request ID) so any call can be traced from the dashboard Logs tab. Response bodies are never stored. Request bodies are stored only if you switch on request-body capture in Settings — then the full request is kept with the log entry and shown when you expand its row in Logs.

POST /v1/responses

The OpenAI Responses API, on the same key, the same routing rules and the same failover as chat completions. Responses is an additional endpoint on the openai and openrouter shapes — not a shape of its own — so one key calls both paths and you configure nothing extra. Keys in the anthropic, google or systemone shapes get the usual 404 wrong_api_shape.

curl https://api.freerouter.com/v1/responses \
  -H "Authorization: Bearer $FREEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o-mini",
    "input": "Reply with exactly one word: pong.",
    "max_output_tokens": 300
  }'

model and input are required; input may be a string or an array of input items. stream: true returns OpenAI Responses server-sent events. Everything else in the standard create payload — instructions, tools, text, reasoning, metadata, temperature — is carried through.

Completions vs Responses, and what it means for routing

This is the part worth understanding before you point production traffic at it.

Gateways do not all implement both endpoints. Most serve Chat Completions. Ramp Router serves only Responses — it has no /chat/completions at all. Rather than refuse the combinations that do not line up, FreeRouter translates whichever direction is needed, per target, per request. The practical result: every gateway on your key can serve /v1/responses, and every gateway can serve /v1/chat/completions. Your choice of gateway never decides which endpoints you may call.

There are two ways a request gets served, and the response header tells you which:

X-FreeRouter-Responses-ModeWhat happened
nativeThe gateway implements /responses. Your body went upstream verbatim and the answer came back untouched.
translatedFreeRouter converted your request to Chat Completions, sent it, and converted the answer back into a Response object (including the full response.* event stream).

Native today, each measured against a live key: Ramp Router, OpenRouter, Requesty, Cloudflare, Darkbloom, Vercel, OrcaRouter, Concentrate, LLM Gateway, Baseten, Sail, Fireworks AI, Morph, Cheaper Inference, Celeris, SayGM, Experiential Labs and NanoGPT. Together AI is the one live gateway that takes the translated path — its Responses support is per model, not per gateway, so every Together model is served by translation (always correct, never byte-faithful). See the per-gateway table under Providers → Responses support.

What this means when you route across gateways. A key with several targets may serve one call natively and the next by translation, depending on which target answered. The reply is the same shape either way, and the streamed event sequence is identical. What differs is provider-specific extras: reasoning summaries, encrypted reasoning content, hosted tools (web search, image generation), annotations and logprobs survive only on a native target. A translated response's id is synthesized by FreeRouter (resp_fr_…) and is not retrievable anywhere. If your application depends on any of those, pin the key to a native gateway; otherwise routing freely is safe.

Chaining with previous_response_id

Chains pin to one gateway and do not fail over FreeRouter returns its own response ids (resp_fr_…). Send one back as previous_response_id and the follow-up is pinned to the gateway that holds the state; only Ramp Router, Cloudflare AI Gateway, LLM Gateway, Fireworks AI and Experiential Labs can anchor a chain, so a chaining client needs one of them on the key. Ids are bound to the creating key and expire after 30 days; conversation is rejected. Setup consequences: Chained turns pin to one provider.
Full chaining reference — how ids work, per-gateway measurements, error codes, retrieval

Chaining works, on gateways that can honor it. It needs care because previous_response_id names a response stored by one specific upstream account — routed anywhere else it is not degraded, it is meaningless.

The response id FreeRouter returns is its own (resp_fr_…), not the gateway's. It is opaque, encrypted, and carries the route back to the provider that holds the state. Send it as previous_response_id and FreeRouter pins the follow-up to that provider and puts the gateway's own id back on the wire. Ids are bound to the API key that created them and expire after 30 days.

A pinned chain does not fail over This is the one place FreeRouter deliberately gives up its headline feature. Failing over mid-chain would send an id the next gateway has never seen, so a pinned turn is tried against its own provider and nothing else; if that provider is down you get its error, not a silent switch. Model remaps and the Jev decisioner are skipped on a pinned turn for the same reason — both exist to move a request elsewhere.

Not every gateway can anchor a chain, and serving /v1/responses natively does not imply it. Verified 2026-09-22: Ramp Router, Cloudflare AI Gateway and LLM Gateway honor previous_response_id — LLM Gateway across all six model families tried (OpenAI, Anthropic, Google, DeepSeek, xAI, Mistral), refusing a fabricated id outright. Fireworks AI joined them on 2026-09-29: context carried across all seven families tried (DeepSeek, Kimi, GLM, gpt-oss, Qwen, MiniMax, Nemotron), a fabricated id refused with 404, and GET /v1/responses/{id} served too. OpenRouter, Baseten and Sail reject it outright — Baseten with a clear 501 and Sail a 400, both saying previous_response_id is not supported on every model — and so do Morph (501) and Cheaper Inference (400, “this endpoint is stateless”), measured 2026-10-01, and Celeris (404, even for an id it just issued), measured 2026-10-08, and SayGM (503; it forces store back to false on every response), measured 2026-10-14. Experiential Labs honors it across GPT, Qwen, Claude and DeepSeek models, refusing a fabricated id with 400 previous_response_not_found, measured 2026-10-14 — so it anchors chains, though it serves no GET /v1/responses/{id}. NanoGPT does not: measured the same day, Qwen carried a secret word, Llama 3.2 3B answered a different word, and DeepSeek and Gemini did not return it, so chaining is not gateway-wide and FreeRouter does not anchor chains there. A fabricated id is refused with 400 response_not_found. Its GET /responses/{id} answers, but retrieval is only offered on a gateway that also chains. Requesty, Darkbloom and the Vercel AI Gateway accept the field and silently ignore it, answering as if the first turn never happened — Vercel most starkly, forcing store back to false, echoing whatever id you send, and returning 200 even for a fabricated one. There is no signal in those responses that the chain was dropped, which is why FreeRouter refuses to chain off them rather than passing it through.

OrcaRouter is the case that explains why this is gateway-wide. It chains correctly for OpenAI-backed models — store honored, context carried, a fabricated id properly rejected — and silently loses context for Anthropic, Google and DeepSeek models, all of which still report store: true. Because chain support has to be decided per gateway before a request is routed, a gateway that can only chain some of its catalog is treated as unable to chain at all. Concentrate is the mirror image — it chains Anthropic, Google and DeepSeek models correctly but rejects OpenAI, xAI and Bedrock ones, and it accepts a fabricated previous_response_id with a 200 instead of refusing it, so an id it had evicted would read as a successful answer with no context. Use Ramp Router, Cloudflare, LLM Gateway, Fireworks AI or Experiential Labs when you need chaining, or resend the full input each turn.

CodeMeaning
invalid_previous_response_idNot an id FreeRouter issued, or altered/truncated. Chain only ids returned by this API.
response_chain_unsupported_targetThe gateway that served the first turn does not store responses.
response_chain_wrong_keyThe id belongs to a different FreeRouter key.
response_chain_expiredOlder than 30 days.
response_chain_target_unavailable409 — that provider is no longer a target on the key.
stateful_responses_unsupportedconversation — FreeRouter does not proxy the Conversations API.

store is forwarded, not rejected. GET /v1/responses/{id} routes back to the originating provider and is served today on LLM Gateway (measured 2026-09-22) and Fireworks AI (2026-09-29), the two gateways measured to support it: pass back the resp_fr_… id you were given and you get the stored Response, with its id rewritten to ours again. Every other gateway answers 501 responses_retrieve_unsupported — Ramp Router has no such route, Cloudflare's /compat endpoint rejects the method, Vercel and Concentrate return 404, and OrcaRouter redirects the request to its marketing domain. Retrieval is a capability of its own: a gateway serving /v1/responses natively, even one that chains, does not necessarily store responses you can fetch back.

Feature support

The endpoint shares routing, failover, model remaps, model aliases, IP allowlists, steering, logging and the X-FreeRouter-* headers with chat completions. These are worth calling out:

FeatureOn /v1/responses
Steering messagesSupported — appended to instructions, the Responses system prompt.
Companion AdsSupported. On a buffered call ads sits beside the Response object. On a stream it is merged into the terminal response.completed event rather than injected as a new event — response.* is a typed union and an unknown event type can throw in a strict parser. Every other record passes through byte for byte.
MCP toolsSupported, including on streams. The agentic loop runs in Responses items (function_call output items in, function_call_output input items back), so a native gateway keeps its verbatim round trip. See Streaming with tools.
Jev decisionerSupported. Input items convert to chat messages exactly, so it scores the real conversation.
Monetizable KeytermsSupported, buffered and streamed.
PlaygroundSupported. Pick Responses on the dashboard Playground to run this endpoint, see which targets are native vs translated, and continue a response where the gateway allows it. The Playground does not execute key-attached MCP tools on this endpoint — the live API does.

Streaming with tools

An agentic loop is multi-turn: call the model, run the tools it asked for, call it again. FreeRouter streams that loop on /v1/responses, /v1/chat/completions and the Anthropic /v1/messages shape — the turns are merged into one response in whichever event vocabulary your key speaks. The Google shape does not stream at all yet, so the question does not arise there.

You still receive exactly one response: one response.created, one terminal event, continuous sequence_numbers and output_indexes. Turns are merged — any prose the model produces along the way ("let me look that up") reaches you in order, while the tool calls FreeRouter ran on your behalf stay hidden. Strict SDK parsers see a single well-formed stream.

Tools you send yourself are never executed by FreeRouter. If you run your own MCP servers client-side — OpenCode, Codex CLI, or anything using the OpenAI SDK's tool loop — your tools ride the request untouched and their function_call items come straight back to you, streamed or buffered, for your client to execute. FreeRouter only runs tools from MCP servers attached to the key in the dashboard, and it can tell the two apart by name. If a single turn mixes both, the whole turn is handed to you rather than half-answered.

Token budgets on reasoning models On the Responses API, max_output_tokens also pays for a reasoning model's hidden reasoning tokens. A budget that looks generous for the visible answer can be spent entirely on reasoning, returning an empty message. FreeRouter reports that honestly rather than hiding it: you get status: "incomplete" with incomplete_details.reason: "max_output_tokens", and usage.output_tokens_details.reasoning_tokens showing where the budget went. Raise max_output_tokens, or lower reasoning.effort.

A runnable example lives in samples/freerouter-responses.js — it prints the output items, the streamed event lifecycle, and which gateway served the call and how.

POST /v1/embeddings

OpenAI-shaped embeddings on openai and openrouter keys: the body (model, input, encoding_format, dimensions, user) is forwarded as sent and the gateway's list of embedding objects is relayed as is. Other shapes get 404 wrong_api_shape.

curl https://api.freerouter.com/v1/embeddings \
  -H "Authorization: Bearer $FREEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/text-embedding-3-small",
    "input": "The quick brown fox"
  }'

Routing is the chat routing: the key's targets in priority or split order, model remaps and aliases applied, failover on 5xx / 429 / 404 / connection errors (the usual reasons). Not every gateway has an embeddings route, so a target whose gateway does not is skipped before any call and shows as gateway:no-embeddings in X-FreeRouter-Failover; a rule with no embeddings-capable target answers 501 embeddings_unsupported, naming the gateways it picked and the ones that do serve it. Served today by OpenRouter, Vercel, Cloudflare, Requesty, Together AI, Fireworks AI and Baseten (from each gateway's published API). The crawled catalogs list chat models, so an embedding id is never skipped as not-listed: the gateway is asked and a 404 fails over. No streaming, steering, decisioner, Companion Ads, keyterms or MCP tools on this endpoint.

Batch API (/v1/batches)

Asynchronous batches at roughly half the per-token price, results within 24 hours: POST /v1/batches with the requests inline, then GET /v1/batches/{id} until the batch completes and carries its results. Also GET /v1/batches (list) and DELETE /v1/batches/{id}. The contract is OpenRouter's Batch API, so an OpenRouter batch client needs only a base-URL change. Runs on your own OpenRouter key; openai and openrouter keys only. Full guide: Batch API.

GET /v1/models

Lists the models available to your key across its attached providers, in the OpenAI list format. Use it to discover valid model strings.

The request fans out in parallel to every routable target on the key — including weight-0 failover-only targets — then merges the catalogs. Duplicate ids keep the first-seen object (listed order). A provider that is unreachable or returns an error is skipped; if every provider fails, you get a 502. Model remaps are not applied: there is no incoming model to match on. Batch-only variants (ids ending in :batch, e.g. OpenRouter's openai/gpt-4o:batch) are left out — gateways serve them only through an asynchronous Batch API, so no endpoint on this key can call them. X-FreeRouter-Provider is set only when a single provider supplied the list; X-FreeRouter-Attempts counts how many catalogs were pulled.

curl https://api.freerouter.com/v1/models \
  -H "Authorization: Bearer $FREEROUTER_API_KEY"
{
  "object": "list",
  "data": [
    { "id": "openai/gpt-4o-mini", "object": "model" },
    { "id": "anthropic/claude-haiku-5.5", "object": "model" }
  ]
}

GET /v1/models/{id}

One model from the same merged catalog, on openai and openrouter keys — what the OpenAI SDKs' models.retrieve() calls. The id may contain slashes. The answer is the OpenAI model object with id, object: "model", created and owned_by guaranteed (the vendor prefix of a slashed id when the gateway sends none) and the gateway's own extra fields kept. An id that none of the key's providers list — or a batch-only :batch id — answers 404 model_not_found; :generateContent paths stay the google shape's POST route.

curl https://api.freerouter.com/v1/models/openai/gpt-4o-mini \
  -H "Authorization: Bearer $FREEROUTER_API_KEY"
{ "id": "openai/gpt-4o-mini", "object": "model", "created": 1721172741, "owned_by": "openai" }

POST /v1/messages/count_tokens

Anthropic's token count, on anthropic keys — what the Anthropic SDKs' messages.count_tokens() and Claude Code call to budget context. Served by FreeRouter itself: the body goes through the same translation as /v1/messages (so system, tools, images and document blocks are accounted the way they would be sent, and the key's steering message is included), nothing goes upstream and nothing is spent. Validation errors use the Anthropic envelope.

curl https://api.freerouter.com/v1/messages/count_tokens \
  -H "x-api-key: $FREEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-haiku-5.5",
    "messages": [{"role": "user", "content": "Hello from FreeRouter!"}]
  }'
{ "input_tokens": 10 }

The number is an estimate, not the serving model's tokenizer (no gateway-neutral one exists), and the response says so in X-FreeRouter-Count-Tokens: estimate. How it counts: text at about 4 characters per token plus a small per-message overhead; images at Anthropic's (width × height) / 750 when the size can be read from a PNG, JPEG, GIF or WebP data URL (capped at the 1568-pixel downscale), otherwise 1,600; PDF documents at 1,600 each; tool definitions by the length of their JSON. Use it to budget context; the billed figure is usage.input_tokens on the real response.

Security

Keep FreeRouter keys server-side: a key can spend against every provider key attached to its routing rule. Rotate a leaked key from the dashboard (the old secret can keep working for a grace window — see X-FreeRouter-Key-Grace-Until above) or revoke it without affecting other keys. To limit where a key can be used from at all, add an IP allowlist.

IP allowlists

A key can be restricted to a fixed set of client addresses, so a leaked key is useless from anywhere else. There are two lists:

  • Workspace list — Settings → IP allowlist. Applies to every key in the workspace.
  • Key list — API Keys → the key's IP allowlist section, or allowed_ips on the management API and MCP server.

Each entry is an IPv4 or IPv6 address or a CIDR range. Separate entries with commas or newlines; up to 100 per list.

203.0.113.7, 198.51.100.0/24, 2001:db8::/32

How the two lists combine:

  • An empty list means no restriction at that level. Both lists start empty, so existing keys keep working until you add entries.
  • When both lists have entries, a request has to pass both. A key's list can narrow the workspace list; it cannot widen it.
  • Single addresses are stored as written (203.0.113.7), ranges as their network address (10.1.2.3/8 saves as 10.0.0.0/8). Clients arriving as IPv4-mapped IPv6 (::ffff:203.0.113.7) match IPv4 entries.
  • Saves take effect on the next request. Nothing in flight is interrupted.

A request from an address outside the list returns 403 with code ip_not_allowed. The message names the address FreeRouter saw, which is the one to add when your servers sit behind NAT or a fixed egress. Rejections appear on the dashboard's Logs tab with status 403 and no upstream call.

{
  "error": {
    "type": "invalid_request_error",
    "code": "ip_not_allowed",
    "message": "Requests from 198.51.100.1 are not allowed by this key's IP allowlist."
  }
}

The dashboard's Test key button and the Playground call your gateways directly with the stored provider key, not through the proxy with your FreeRouter key, so they keep working whatever the list says.

Scope IP allowlists cover FreeRouter keys (fr_live_…) on api.freerouter.com. Management keys and the MCP server are not restricted by them.

API shapes

A shape is the request/response format a FreeRouter key speaks. You choose it per key so FreeRouter can be a true drop-in for whatever client you already run. The base URL is the same for every shape; only the path and body conventions differ.

ShapeSpeaks likePrimary pathAlso serves
openai (default)OpenAI Chat Completions/v1/chat/completions/v1/responses, /v1/embeddings, /v1/models/{id}, /v1/batches
openrouterOpenRouter Chat Completions/v1/chat/completions/v1/responses, /v1/embeddings, /v1/models/{id}, /v1/batches
anthropicAnthropic Messages/v1/messages/v1/messages/count_tokens
googleGoogle Gemini generateContent/v1/models/{model}:generateContent—
systemoneSystem One / Decisions/v1/systemone—

A shape is a dialect; some dialects serve more than one endpoint. openai and openrouter keys speak Chat Completions, the Responses API and embeddings — there is no separate "responses" or "embeddings" shape to choose, and choosing a shape never limits which gateways you can route to. GET /v1/models answers on every shape.

Set the shape when you create the key, or toggle it later in the dashboard — no code change beyond matching the client you already use. FreeRouter translates between the shape your key speaks and the format each gateway expects.

openai (default)

The de facto standard. Works with the OpenAI SDKs and any OpenAI-compatible client. This is the shape used throughout the Quickstart.

openrouter

Accepts OpenRouter-style requests. Handy when you're migrating an app already built against OpenRouter and want to keep its request bodies verbatim.

OpenRouter's models and provider fields The openrouter shape forwards your request body verbatim, so when OpenRouter serves the request it honors models and provider itself, exactly as today. FreeRouter's own routing reads only model: models and provider never decide which gateway is tried, and if the request fails over to a non-OpenRouter target those fields reach a gateway that ignores them. Express fallback with the key's routing rule instead — it works across every gateway.

anthropic

Speaks the Anthropic Messages API. Point an Anthropic client at FreeRouter and call /v1/messages. With an Anthropic SDK, set the base URL without /v1 (the SDK adds /v1/messages itself):

import os
from anthropic import Anthropic

client = Anthropic(base_url="https://api.freerouter.com", api_key=os.environ["FREEROUTER_API_KEY"])

The same goes for Claude Code: ANTHROPIC_BASE_URL=https://api.freerouter.com plus ANTHROPIC_API_KEY=fr_live_…. Streams report real input_tokens / output_tokens in message_delta.usage, and send ping events while a model is thinking so idle-timeouts don't cut long answers. With curl:

curl https://api.freerouter.com/v1/messages \
  -H "Authorization: Bearer $FREEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "anthropic/claude-haiku-5.5",
    "max_tokens": 256,
    "messages": [{ "role": "user", "content": "Ping" }]
  }'

The request is translated to the gateway's chat format and the answer back. What that carries, beyond text, images, tools and sampling:

Messages API featureWhat FreeRouter does
Extended thinkingthinking.budget_tokens goes upstream as the gateway's reasoning.max_tokens; the model's reasoning comes back as thinking content blocks (streamed as thinking_delta, ahead of the text). thinking / redacted_thinking blocks replayed in history are stripped, so an agent's second turn works.
document blocks (PDF)Sent as the chat file content part (base64, URL or file_id); gateways that take files (OpenRouter) read them, others return their own error. Plain-text document sources ride inline.
Server tools (web_search_*, text_editor_*, …)Passed through as sent, for the gateway to run or refuse. server_tool_use and *_tool_result blocks from an earlier turn are dropped; the assistant text that cited them is kept.
tool_choice.disable_parallel_tool_useBecomes parallel_tool_calls: false. An image inside a tool_result follows the tool messages as a user message.
stop_reasonend_turn, max_tokens, tool_use, refusal (the gateway's content filter) and stop_sequence with stop_sequence set, detected from the tail of the text — a gateway that trims the sequence reports end_turn.
Error typesinvalid_request_error (400), authentication_error (401), permission_error (403), not_found_error (404), request_too_large (413), rate_limit_error (429), overloaded_error (503, 529), api_error (other 5xx).
Droppedtop_k, metadata, service_tier, cache_control — no chat-completions equivalent.

google

Speaks the Google Gemini generateContent format, so a Gemini client can target FreeRouter with the model in the path:

curl "https://api.freerouter.com/v1/models/google/gemini-2.5-flash-lite:generateContent" \
  -H "Authorization: Bearer $FREEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contents": [{ "parts": [{ "text": "Ping" }] }]
  }'

Streaming (:streamGenerateContent) is not supported for the google shape yet — call :generateContent without streaming, or use an openai-shaped key for the same model.

systemone

FreeRouter's Decisions contract — the same wire TypeSafe uses natively: send model, state and typed questions to POST /v1/systemone, receive answers with calibrated probabilities. Hosts are TypeSafe, OpenRouter, Requesty, Cloudflare, OpenAI, Vercel, Celeris, and Ramp Router. Full reference, including images in state and multi-vendor decision models: System One shape.

Errors

A stream that breaks off after it started can't change its status, so FreeRouter ends it with an error event instead of a normal finish: data: {"error":{"code":"stream_aborted",…}} with no [DONE] on chat completions, event: error on Anthropic and Responses streams. Treat it as a failed request.

Errors use standard HTTP status codes, except 499. That one is written to Logs when your client hangs up, and it is not a response body — the socket is already gone. The envelope for every other error mirrors your key's API shape: openai/openrouter keys get the OpenAI-style body below, anthropic keys get {"type": "error", "error": {"type", "message"}}, and google keys get {"error": {"code", "message", "status"}}.

StatusMeaning
400Malformed request body or parameters (missing model/messages, invalid shape fields). Also batch_only_model: the model is a batch-only :batch variant — submit it to the Batch API, or send the id without :batch.
400 invalid_jsonThe body is not valid JSON.
401Missing or invalid FreeRouter key (invalid_api_key), or a revoked one (revoked_api_key).
402No way to spend: the key has no routing targets configured (no_route), or an upstream gateway rejected the request for billing reasons (relayed).
403The client address is outside the key's or the workspace's IP allowlist (code ip_not_allowed). The message names the address FreeRouter saw. Also workspace_suspended.
404Unknown endpoint — or unknown model, two ways: model_not_available when FreeRouter's catalogs say none of the key's targets lists the model, so nothing was sent and the message names which gateways do carry it (see Model availability); model_not_found when every target was called and each answered 404 — the last gateway's own error message is relayed, in your key's envelope. wrong_api_shape: the endpoint belongs to another API shape than the key's (e.g. /v1/messages with an openai key).
429Rate limited: every eligible target returned 429. (FreeRouter itself imposes no rate limit during the preview.)
499client_closed — Logs only. Your client disconnected before FreeRouter finished the response (a client timeout, or the request was cancelled). No HTTP 499 is sent. The Status column on a log row is this outcome. A hop in the expanded route can still say 200 served: that badge is the gateway's own HTTP status. The gateway accepted the call and started the reply, then the client left mid-stream. FreeRouter cancels the upstream request, including streams and MCP tool rounds, and tries no further targets. An Upstream ×N beside a single hop means the same thing — each MCP round counts as an attempt, and the row keeps the last hop. Raise the client timeout, or stream so bytes arrive early.
501None of this key's targets are routable yet — the stored gateways have no live adapter (gateway_not_supported). Point routing at a live gateway. On /v1/embeddings, embeddings_unsupported: none of the rule's gateways has an embeddings route; the message names which do.
413request_too_large: the body is larger than the limit (10 MB). Send large files by URL, or split the request. Not retried by SDKs.
502All eligible gateways failed after failover retries. provider_auth_failed: the last gateway rejected the provider key stored for it (401) — re-paste it under Providers. upstream_unreadable: the gateway answered but its body broke off before it could be read. no_healthy_target: every provider key on the rule is disabled or missing.
503gateway_disabled: every target's gateway is switched off platform-wide for now. Nothing to fix on your side; try again later.
504deadline_exceeded: the 95 s request deadline ran out before a gateway answered — the targets tried first used up the time. Put a faster target first or shorten its failover timeout.
{
  "error": {
    "type": "invalid_request_error",
    "message": "Unknown model: foo/bar"
  }
}

MCP Tools

When MCP servers are enabled on a key (Settings → MCP, then Enable MCPs on the key), a request on any shape (/v1/chat/completions, /v1/responses, /v1/messages, generateContent) can offer those tools to the model, execute tool_calls, and fold results into the reply. The client request is unchanged — no extra body field. Streaming runs the loop on the OpenAI shapes (/v1/chat/completions and /v1/responses) and on Anthropic /v1/messages, merging the turns into one response; the Google shape has no streaming at all yet. Tools you send yourself are never executed by FreeRouter — they come back for your client to run. A down MCP server never fails inference. See MCP Tools for Settings, per-key opt-in, and BYOK.

Companion Ads

When Companion Ads is enabled on a key, send an ad_request object as a sibling of the usual body (it is not part of the API shape). FreeRouter strips it before the LLM gateway sees it, fetches ads in parallel with the model call (using the inbound conversation), and on a successful LLM response attaches ads or ads_error. A no-fill is ads: [], not an error. Missing or invalid ad params never fail inference. See Companion Ads for the request fields, response shape, streaming event, and Settings setup.

Monetizable Keyterms

When Monetizable Keyterms is enabled on a key, FreeRouter extracts scored entities (brands, products, model numbers) from the completed assistant reply and attaches keyterms or keyterms_error on a successful LLM response. Optionally send a link_request object (e.g. {"max_keyterms": 3}) as a sibling of the usual body — it is not part of the API shape and is stripped before the LLM gateway sees it. Each candidate carries a destination url; your app decides which spans to hyperlink. Extraction problems never fail inference. See Monetizable Keyterms for the request fields, response shape, streaming event, and destination template.

Next Companion Ads