OpenRouter Free code examples in C#, Python, Java and JavaScript
One OpenAI-compatible endpoint in front of hundreds of models.
WHAT THIS PAGE COVERS
Automatic fallback when a provider is down or rate-limits you.
anthropic/claude-sonnet-4.6
openai
Model ids change. If a sample returns a 404 on the model, that is the first thing to check against the official docs.
The official OpenAI package with the endpoint overridden. No OpenRouter SDK exists and none is needed.
dotnet add package OpenAI
setx OPENROUTER_API_KEY "sk-or-v1-your-key" # Windows, then reopen
export OPENROUTER_API_KEY="sk-or-v1-your-key" # macOS / Linux
Endpoint on the options, model id with its vendor prefix. Everything else is the OpenAI SDK you already know.
using System.ClientModel;
using OpenAI;
using OpenAI.Chat;
var apiKey = Environment.GetEnvironmentVariable("OPENROUTER_API_KEY")
?? throw new InvalidOperationException("OPENROUTER_API_KEY is not set.");
var options = new OpenAIClientOptions
{
Endpoint = new Uri("https://openrouter.ai/api/v1"),
};
ChatClient chat = new("anthropic/claude-sonnet-4.6",
new ApiKeyCredential(apiKey), options);
ChatCompletion completion = await chat.CompleteChatAsync(
"Explain a database index in two sentences.");
Console.WriteLine(completion.Content[0].Text);
`models` is not an OpenAI parameter, so the typed ChatClient has nowhere to put it. Send the request yourself — it is one HttpClient call and it keeps the fallback list visible.
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
http.DefaultRequestHeaders.Add("HTTP-Referer", "https://freeyantra.com");
http.DefaultRequestHeaders.Add("X-OpenRouter-Title", "FreeYantra");
var response = await http.PostAsJsonAsync(
"https://openrouter.ai/api/v1/chat/completions",
new
{
model = "anthropic/claude-sonnet-4.6",
// Max three. The last one is the floor you are happy to ship.
models = new[] { "openai/gpt-5.4-mini", "google/gemini-2.5-flash" },
messages = new[]
{
new { role = "user", content = "Explain a database index." },
},
});
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadFromJsonAsync<JsonElement>();
Console.WriteLine($"served by: {body.GetProperty("model").GetString()}");
A fallback can be an order of magnitude more expensive than the first choice. The generation endpoint gives the real number against a completion id.
var id = body.GetProperty("id").GetString();
var details = await http.GetFromJsonAsync<JsonElement>(
$"https://openrouter.ai/api/v1/generation?id={id}");
var data = details.GetProperty("data");
Console.WriteLine(
$"{data.GetProperty("model").GetString()} " +
$"cost {data.GetProperty("total_cost").GetDouble():F6} " +
$"({data.GetProperty("tokens_completion").GetInt32()} completion tokens)");
No OpenRouter SDK. It is the OpenAI client with a different base URL, which is the whole point.
pip install openai
export OPENROUTER_API_KEY="sk-or-v1-your-key" # macOS / Linux
setx OPENROUTER_API_KEY "sk-or-v1-your-key" # Windows, then reopen
The attribution headers are optional and worth sending — they put your app on the public rankings. Note the title header is X-OpenRouter-Title; the older X-Title is ignored.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
completion = client.chat.completions.create(
extra_headers={
"HTTP-Referer": "https://freeyantra.com",
"X-OpenRouter-Title": "FreeYantra", # not X-Title
},
model="anthropic/claude-sonnet-4.6",
messages=[{"role": "user", "content": "Explain a database index."}],
)
print(completion.choices[0].message.content)
MAX THREE ENTRIES; a longer list is a 400. With the OpenAI SDK it goes in extra_body because it is not a parameter the SDK knows. Triggers are downtime, rate limits, context-length errors and moderation — not a malformed request, which comes straight back.
completion = client.chat.completions.create(
model="anthropic/claude-sonnet-4.6", # first choice
extra_body={
"models": [
"openai/gpt-5.4-mini", # second
"google/gemini-2.5-flash", # LAST = the floor you
], # are happy to ship
},
messages=[{"role": "user", "content": "Explain a database index."}],
)
# Billing follows whichever model actually answered, so read it back
# rather than assuming you got your first choice.
print("served by:", completion.model)
A different axis entirely: same weights, different company running them. order sets the preference, only bounds the set, and allow_fallbacks (default true) decides whether OpenRouter may step outside your order.
completion = client.chat.completions.create(
model="meta-llama/llama-3.3-70b-instruct",
extra_body={
"provider": {
"order": ["together", "fireworks"], # preference
"allow_fallbacks": True, # default
},
},
messages=[{"role": "user", "content": "Explain a database index."}],
)
# To pin routing completely: one model, no `models` array, and
# allow_fallbacks false. Then a failure is a failure — which is what
# you want when the answer must come from a specific host for
# compliance reasons.
A fallback can change the price by an order of magnitude. The generation endpoint returns the real cost for a completion id — log it, or the first surprise is the invoice.
import requests
details = requests.get(
f"https://openrouter.ai/api/v1/generation?id={completion.id}",
headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"},
timeout=30,
).json()["data"]
print(
details["model"],
details["total_cost"],
details["tokens_prompt"],
details["tokens_completion"],
)
# Failed requests are not billed. Aborting a STREAM mid-flight still
# is, on several providers — worth knowing before adding a timeout
# that cancels generations.
openai-java with the base URL overridden. The attribution headers go on as default headers.
# Gradle
implementation("com.openai:openai-java:4.42.0")
export OPENROUTER_API_KEY="sk-or-v1-your-key"
baseUrl plus the key. Model ids carry a vendor prefix — anthropic/, openai/, google/ — and the prefix is part of the id.
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.chat.completions.ChatCompletionCreateParams;
public class Ask {
public static void main(String[] args) {
String apiKey = System.getenv("OPENROUTER_API_KEY");
if (apiKey == null) throw new IllegalStateException("OPENROUTER_API_KEY is not set.");
OpenAIClient client = OpenAIOkHttpClient.builder()
.baseUrl("https://openrouter.ai/api/v1")
.apiKey(apiKey)
.putHeader("HTTP-Referer", "https://freeyantra.com")
.putHeader("X-OpenRouter-Title", "FreeYantra")
.build();
ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
.model("anthropic/claude-sonnet-4.6")
.addUserMessage("Explain a database index in two sentences.")
.build();
client.chat().completions().create(params).choices()
.forEach(choice -> System.out.println(choice.message().content().orElse("")));
}
}
`models` is not an OpenAI field, so it goes on as an additional body property. Three entries maximum — a longer list is rejected with a 400.
import com.openai.core.JsonValue;
import java.util.List;
ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
.model("anthropic/claude-sonnet-4.6")
.addUserMessage("Explain a database index in two sentences.")
// Not a typed parameter — it rides along in the body.
.putAdditionalBodyProperty(
"models",
JsonValue.from(List.of(
"openai/gpt-5.4-mini",
"google/gemini-2.5-flash"))) // last = the floor
.build();
var completion = client.chat().completions().create(params);
// Which model answered, and therefore what you are paying for.
System.out.println("served by: " + completion.model());
For a request that has to come from one specific host — compliance, data residency, a contract — send one model, no fallback list, and allow_fallbacks false. Then a failure is a failure rather than a silent substitution.
import java.util.Map;
ChatCompletionCreateParams pinned = ChatCompletionCreateParams.builder()
.model("meta-llama/llama-3.3-70b-instruct")
.addUserMessage("Explain a database index in two sentences.")
.putAdditionalBodyProperty(
"provider",
JsonValue.from(Map.of(
"only", List.of("together"),
// Default is true: OpenRouter may step outside your order.
"allow_fallbacks", false)))
.build();
// No `models` array either — nothing may substitute the model.
var completion = client.chat().completions().create(pinned);
The OpenAI package, a different base URL. Nothing OpenRouter-specific to install or keep up to date.
npm install openai
export OPENROUTER_API_KEY="sk-or-v1-your-key"
defaultHeaders carries the attribution once rather than per request. The title header is X-OpenRouter-Title — X-Title is the old name and is ignored.
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY,
defaultHeaders: {
"HTTP-Referer": "https://freeyantra.com",
"X-OpenRouter-Title": "FreeYantra",
},
});
const completion = await client.chat.completions.create({
model: "anthropic/claude-sonnet-4.6",
messages: [{ role: "user", content: "Explain a database index." }],
});
console.log(completion.choices[0].message.content);
Three entries maximum. The list is walked once in order and stops — it is not an infinite retry chain, and if the last one errors you get that error.
const completion = await client.chat.completions.create({
model: "anthropic/claude-sonnet-4.6",
// Not in the OpenAI types, so it rides along as an extra field.
models: [
"openai/gpt-5.4-mini",
"google/gemini-2.5-flash", // LAST = the floor
],
messages: [{ role: "user", content: "Explain a database index." }],
});
// Which one answered — and therefore what you are being billed for.
console.log("served by:", completion.model);
A substitute model changes tokenizer, context, tool behaviour and price. If the answer feeds anything automated, ASSERT ON WHAT CAME BACK rather than trusting that a fallback is equivalent.
const ACCEPTABLE = new Set([
"anthropic/claude-sonnet-4.6",
"openai/gpt-5.4-mini",
"google/gemini-2.5-flash",
]);
async function ask(messages) {
const completion = await client.chat.completions.create({
model: "anthropic/claude-sonnet-4.6",
models: ["openai/gpt-5.4-mini", "google/gemini-2.5-flash"],
messages,
});
if (!ACCEPTABLE.has(completion.model)) {
// Routing changed under you. Better to know than to silently
// serve output from a model nobody evaluated.
console.warn(`unexpected model: ${completion.model}`);
}
return completion;
}
The generation endpoint returns the real cost for a completion id. Log the model and the cost together, because a fallback can be ten times the price of the first choice.
const details = await fetch(
`https://openrouter.ai/api/v1/generation?id=${completion.id}`,
{ headers: { Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}` } },
).then((response) => response.json());
console.log(JSON.stringify({
model: details.data.model,
cost: details.data.total_cost,
prompt: details.data.tokens_prompt,
completion: details.data.tokens_completion,
}));
// Failed requests are not billed. An aborted STREAM still is, on
// several providers.
QUESTIONS
Which languages are the OpenRouter examples in?
C#, Python, Java and JavaScript. Every sample runs as pasted and reads its credentials from an environment variable — no key is ever written into the code.
Which package do I install for OpenRouter?
The samples use openai. The install step on each language tab gives the exact command.
Which OpenRouter model do these examples use?
anthropic/claude-sonnet-4.6, checked against the official documentation on 26 September 2026. Model ids change often; if a call returns a model-not-found error, check the current list in the official docs.
Where is the official OpenRouter documentation?
https://openrouter.ai/docs — it is linked at the bottom of this page, and it is the authority whenever it disagrees with a sample here.
ALSO IN HOSTING, GATEWAYS & LOCAL
The authority
Official OpenRouter documentation
Model names, pricing and rate limits change faster than any page here can. When this page and the official docs disagree, the official docs are right.
https://openrouter.ai/docs