Use your SDK

Keep the SDK you have. Each tab shows the two values to change — base URL and key — then a first call and its streaming variant. Model ids are the ones the dashboard pre-fills: openrouter/free answers on any OpenRouter key; swap in a paid id like openai/gpt-4o-mini once the gateway account has credit.

Pick the key's shape to match the SDK OpenAI, Vercel AI SDK, LangChain, LiteLLM and the Responses API need an 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

BeforeAfter
base_urlhttps://api.openai.com/v1https://api.freerouter.com/v1
api_keysk-…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.

BeforeAfter
baseURLhttps://api.openai.com/v1https://api.freerouter.com/v1
apiKeysk-…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.

BeforeAfter
base_urlhttps://api.anthropic.comhttps://api.freerouter.com — no /v1; the SDK adds /v1/messages
api_keysk-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.

BeforeAfter
baseURLhttps://api.anthropic.comhttps://api.freerouter.com — no /v1
apiKeysk-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.

BeforeAfter
ANTHROPIC_BASE_URL(unset)https://api.freerouter.com
ANTHROPIC_API_KEYsk-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.

BeforeAfter
provideropenai from @ai-sdk/openaicreateOpenAI({ baseURL: "https://api.freerouter.com/v1", apiKey })
apiKeyOPENAI_API_KEYfr_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.

BeforeAfter
base_url(default, api.openai.com)https://api.freerouter.com/v1
api_keyOPENAI_API_KEYfr_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.

BeforeAfter
modelgpt-4o-miniopenai/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)
BeforeAfter
base_urlhttps://api.openai.com/v1https://api.freerouter.com/v1 — POST /v1/responses is served on openai and openrouter keys
api_keysk-…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.

BeforeAfter
endpointhttps://generativelanguage.googleapis.com/v1beta/models/{model}:generateContenthttps://api.freerouter.com/v1/models/{model}:generateContent
x-goog-api-keyAIza…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:

BeforeAfter
OPENAI_BASE_URL(unset)https://api.freerouter.com/v1
OPENAI_API_KEYsk-…fr_live_… (shape openai)
ANTHROPIC_BASE_URL(unset)https://api.freerouter.com
ANTHROPIC_API_KEYsk-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-Provider naming 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.
Next Troubleshooting