Use your SDK
openai-shaped key (openrouter works too). The Anthropic SDK and Claude Code need an anthropic-shaped key; Gemini clients need a google-shaped key. Create one per shape in API Keys — the shape is a per-key setting, not a per-request one. First-time setup is in Getting Started.
Snippets by SDK
| Before | After | |
|---|---|---|
base_url | https://api.openai.com/v1 | https://api.freerouter.com/v1 |
api_key | sk-… | fr_live_… (shape openai) |
# pip install openai
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.freerouter.com/v1", api_key=os.environ["FREEROUTER_API_KEY"])
resp = client.chat.completions.create(
model="openrouter/free",
messages=[{"role": "user", "content": "Hello from FreeRouter!"}],
)
print(resp.choices[0].message.content)
Streaming
stream = client.chat.completions.create(
model="openrouter/free",
messages=[{"role": "user", "content": "Count to 3."}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="", flush=True)
Embeddings and model lookup work on the same client: client.embeddings.create(model="openai/text-embedding-3-small", input="…") and client.models.retrieve("openai/gpt-4o-mini") — see which gateways serve embeddings.
| Before | After | |
|---|---|---|
baseURL | https://api.openai.com/v1 | https://api.freerouter.com/v1 |
apiKey | sk-… | fr_live_… (shape openai) |
// npm install openai
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.freerouter.com/v1", apiKey: process.env.FREEROUTER_API_KEY });
const resp = await client.chat.completions.create({
model: "openrouter/free",
messages: [{ role: "user", content: "Hello from FreeRouter!" }],
});
console.log(resp.choices[0].message.content);
Streaming
const stream = await client.chat.completions.create({
model: "openrouter/free",
messages: [{ role: "user", content: "Count to 3." }],
stream: true,
});
for await (const chunk of stream) process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
Embeddings and model lookup work on the same client: client.embeddings.create({ model: "openai/text-embedding-3-small", input: "…" }) and client.models.retrieve("openai/gpt-4o-mini") — see which gateways serve embeddings.
| Before | After | |
|---|---|---|
base_url | https://api.anthropic.com | https://api.freerouter.com — no /v1; the SDK adds /v1/messages |
api_key | sk-ant-… | fr_live_… (shape anthropic) — sent as x-api-key, which FreeRouter accepts |
# pip install anthropic
import os
from anthropic import Anthropic
client = Anthropic(base_url="https://api.freerouter.com", api_key=os.environ["FREEROUTER_API_KEY"])
msg = client.messages.create(
model="openrouter/free",
max_tokens=256,
messages=[{"role": "user", "content": "Hello from FreeRouter!"}],
)
print(msg.content[0].text)
Streaming
with client.messages.stream(
model="openrouter/free",
max_tokens=256,
messages=[{"role": "user", "content": "Count to 3."}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
Extended thinking (thinking={"type": "enabled", "budget_tokens": …}) comes back as thinking blocks, and document blocks (PDFs) reach gateways that take files; what else the translation carries is in API Reference → anthropic. client.messages.count_tokens(…) answers locally with an estimate, nothing spent.
| Before | After | |
|---|---|---|
baseURL | https://api.anthropic.com | https://api.freerouter.com — no /v1 |
apiKey | sk-ant-… | fr_live_… (shape anthropic) |
// npm install @anthropic-ai/sdk
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ baseURL: "https://api.freerouter.com", apiKey: process.env.FREEROUTER_API_KEY });
const msg = await client.messages.create({
model: "openrouter/free",
max_tokens: 256,
messages: [{ role: "user", content: "Hello from FreeRouter!" }],
});
console.log(msg.content[0].text);
Streaming
const stream = client.messages.stream({
model: "openrouter/free",
max_tokens: 256,
messages: [{ role: "user", content: "Count to 3." }],
});
stream.on("text", (text) => process.stdout.write(text));
await stream.finalMessage();
Extended thinking (thinking: { type: "enabled", budget_tokens: … }) comes back as thinking blocks, and document blocks (PDFs) reach gateways that take files; what else the translation carries is in API Reference → anthropic.
| Before | After | |
|---|---|---|
ANTHROPIC_BASE_URL | (unset) | https://api.freerouter.com |
ANTHROPIC_API_KEY | sk-ant-… | fr_live_… (shape anthropic) |
export ANTHROPIC_BASE_URL="https://api.freerouter.com"
export ANTHROPIC_API_KEY="fr_live_your_key_here"
claude
Claude Code sends the key as x-api-key. ANTHROPIC_AUTH_TOKEN (sent as Authorization: Bearer) works as well. It streams every turn, and FreeRouter keeps long thinking alive with ping events on this shape; a model's reasoning streams as thinking blocks, and the thinking blocks Claude Code replays on later turns are stripped before the gateway sees them. Claude Code asks for Anthropic's own model ids; route the key to a gateway that lists them, or add a model remap from the id Claude Code sends to the provider/model your gateway serves. Its context-budget calls to /v1/messages/count_tokens are answered locally with an estimate.
| Before | After | |
|---|---|---|
| provider | openai from @ai-sdk/openai | createOpenAI({ baseURL: "https://api.freerouter.com/v1", apiKey }) |
apiKey | OPENAI_API_KEY | fr_live_… (shape openai) |
// npm install ai @ai-sdk/openai (AI SDK 5 / @ai-sdk/openai 2.x)
import { createOpenAI } from "@ai-sdk/openai";
import { generateText } from "ai";
const freerouter = createOpenAI({ baseURL: "https://api.freerouter.com/v1", apiKey: process.env.FREEROUTER_API_KEY });
const { text } = await generateText({
model: freerouter.chat("openrouter/free"),
prompt: "Hello from FreeRouter!",
});
console.log(text);
Streaming
import { streamText } from "ai";
const result = streamText({ model: freerouter.chat("openrouter/free"), prompt: "Count to 3." });
for await (const part of result.textStream) process.stdout.write(part);
freerouter.chat(id) calls /v1/chat/completions. In AI SDK 5, freerouter(id) calls /v1/responses instead — FreeRouter serves both on an openai-shaped key.
| Before | After | |
|---|---|---|
base_url | (default, api.openai.com) | https://api.freerouter.com/v1 |
api_key | OPENAI_API_KEY | fr_live_… (shape openai) |
# pip install langchain-openai (0.3.x)
import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="openrouter/free",
base_url="https://api.freerouter.com/v1",
api_key=os.environ["FREEROUTER_API_KEY"],
)
print(llm.invoke("Hello from FreeRouter!").content)
Streaming
for chunk in llm.stream("Count to 3."):
print(chunk.content, end="", flush=True)
LangChain.js: new ChatOpenAI({ model, apiKey, configuration: { baseURL: "https://api.freerouter.com/v1" } }) from @langchain/openai. Embeddings: OpenAIEmbeddings(model="openai/text-embedding-3-small", base_url=…, api_key=…) calls /v1/embeddings on the same key — route it to a gateway that serves embeddings.
| Before | After | |
|---|---|---|
model | gpt-4o-mini | openai/openrouter/free — LiteLLM's openai/ prefix selects its OpenAI-compatible client; the rest is the id FreeRouter sees |
api_base / api_key | (default) | https://api.freerouter.com/v1 / fr_live_… (shape openai) |
# pip install litellm
import os, litellm
resp = litellm.completion(
model="openai/openrouter/free",
api_base="https://api.freerouter.com/v1",
api_key=os.environ["FREEROUTER_API_KEY"],
messages=[{"role": "user", "content": "Hello from FreeRouter!"}],
)
print(resp.choices[0].message.content)
Streaming
for chunk in litellm.completion(
model="openai/openrouter/free",
api_base="https://api.freerouter.com/v1",
api_key=os.environ["FREEROUTER_API_KEY"],
messages=[{"role": "user", "content": "Count to 3."}],
stream=True,
):
print(chunk.choices[0].delta.content or "", end="", flush=True)
| Before | After | |
|---|---|---|
base_url | https://api.openai.com/v1 | https://api.freerouter.com/v1 — POST /v1/responses is served on openai and openrouter keys |
api_key | sk-… | fr_live_… |
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.freerouter.com/v1", api_key=os.environ["FREEROUTER_API_KEY"])
resp = client.responses.create(model="openrouter/free", input="Hello from FreeRouter!")
print(resp.output_text)
Streaming
stream = client.responses.create(model="openrouter/free", input="Count to 3.", stream=True)
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
Node is the same calls on client.responses.create({ model, input, stream: true }). Gateways that serve Responses natively answer byte-for-byte; the rest are translated from chat — X-FreeRouter-Responses-Mode says native or translated. previous_response_id works on chain-capable gateways only: Responses chaining.
| Before | After | |
|---|---|---|
| endpoint | https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent | https://api.freerouter.com/v1/models/{model}:generateContent |
x-goog-api-key | AIza… | fr_live_… (shape google); ?key= also accepted on this route |
curl https://api.freerouter.com/v1/models/openrouter/free:generateContent \
-H "x-goog-api-key: $FREEROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contents": [{"role": "user", "parts": [{"text": "Hello from FreeRouter!"}]}]}'
Streaming
Not served: :streamGenerateContent answers 400 on the google shape. Stream through an openai-shaped key instead.
Only /v1/models/{model}:generateContent is served. The official google-genai SDK defaults to /v1beta, which FreeRouter answers with 404 not_found; point it at base_url="https://api.freerouter.com" with api_version="v1" in its http_options so it builds the /v1/models/…:generateContent path, and keep to generate_content (not the stream variant).
Both official OpenAI SDKs read these at construction, so an app you cannot edit still migrates with two environment variables:
| Before | After | |
|---|---|---|
OPENAI_BASE_URL | (unset) | https://api.freerouter.com/v1 |
OPENAI_API_KEY | sk-… | fr_live_… (shape openai) |
ANTHROPIC_BASE_URL | (unset) | https://api.freerouter.com |
ANTHROPIC_API_KEY | sk-ant-… | fr_live_… (shape anthropic) |
export OPENAI_BASE_URL="https://api.freerouter.com/v1"
export OPENAI_API_KEY="fr_live_your_key_here"
python app.py # OpenAI() with no arguments now talks to FreeRouter
Verify the swap
- The response carries
X-FreeRouter-Providernaming the gateway that answered. - A 401, 402 or 404 on the first call: Troubleshooting covers each with the fix.
- Headers, limits and endpoints that behave differently behind FreeRouter: What's different.