Integrating OpenRouter into a Real Application
This is Part 2 of a five-part series on building production-ready AI applications.
In the previous article, I talked about why I stopped juggling multiple AI SDKs and settled on OpenRouter as the abstraction layer between my applications and different LLM providers.
Now comes the fun part.
The actual integration.
If you've ever integrated the OpenAI SDK before, then you'll feel right at home because, surprisingly, not much changes.
And honestly, that's one of my favorite things about OpenRouter.
You don't install another SDK.
You don't learn another API.
You don't rewrite your application.
You simply point your existing client somewhere else.
Step 1: Set up your client
If you're already using the official OpenAI SDK, your client initialization changes very little.
//typescript
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.OPENROUTER_API_KEY,
baseURL: "https://openrouter.ai/api/v1",
});
That's it.
Instead of pointing to OpenAI's endpoint, you're pointing to OpenRouter's gateway.
From your application's perspective, nothing else really changes.
And that's exactly the point.
OpenRouter doesn't try to replace the OpenAI API.
It implements it.
Why the Base URL matters
At first glance, changing a single URL doesn't seem like much.
But that one line completely changes what your application is capable of.
Yesterday, your requests could only reach OpenAI.
Today, the same client can talk to Claude, Gemini, Llama, DeepSeek, Mistral, Qwen, and hundreds of other models without installing another dependency.
That's the power of abstraction.
Your application doesn't care where the response comes from.
It only cares that it receives one.
Don't ignore the headers
One thing many developers skip is the optional headers OpenRouter recommends.
//typescript
const client = new OpenAI({
apiKey: process.env.OPENROUTER_API_KEY,
baseURL: "https://openrouter.ai/api/v1",
defaultHeaders: {
"HTTP-Referer": "https://yourapp.com",
"X-Title": "My AI Application",
},
});
These headers aren't required for your application to work.
However, they make a huge difference when you're monitoring usage inside the OpenRouter dashboard.
Think of them as metadata.
Instead of seeing anonymous API traffic, you'll know exactly which application generated which requests.
If you're managing multiple projects, you'll thank yourself later.
Model switching becomes configuration
Here's where OpenRouter really starts to shine.
Normally, switching providers means changing SDKs.
Sometimes it means changing request payloads.
Occasionally it means rewriting parts of your application altogether.
With OpenRouter, switching models is often as simple as changing one string.
//typescript
const completion = await client.chat.completions.create({
model: process.env.MODEL_NAME,
messages,
});
Yesterday:
MODEL_NAME=openai/gpt-4o
Today:
MODEL_NAME=anthropic/claude-3.7-sonnet
Tomorrow:
MODEL_NAME=deepseek/deepseek-r1
No deployment.
No new SDK.
No wrapper functions.
Just configuration.
That's a huge win for maintainability.
What actually happens after you send a request?
This is probably the most overlooked part of OpenRouter.
When your application sends a request, it doesn't simply forward the payload.
It interprets it.
Your application sends a standard OpenAI-compatible request.
OpenRouter receives it.
Then it translates that request into whatever format the target provider expects.
Claude has its own conventions.
Gemini has slightly different expectations.
Reasoning models expose different parameters.
OpenRouter smooths over those differences before forwarding the request upstream.
When the provider responds, OpenRouter performs the reverse operation.
It normalizes the response back into the OpenAI format your application already understands.
From your code's perspective, every provider behaves like OpenAI.
That's why your application code stays clean while still supporting multiple providers.
Building for failure
Production systems fail.
Rate limits happen.
Providers experience outages.
Network latency spikes.
The question isn't whether they'll happen.
It's whether your application is prepared when they do.
One of my favorite OpenRouter features is model fallbacks.
Instead of depending on a single provider, you can prioritize multiple models.
//typescript
{
model: "anthropic/claude-3-opus",
models: [
"anthropic/claude-3-opus",
"google/gemini-pro",
"meta-llama/llama-3-70b"
]
}
Imagine Claude temporarily returns a 429 Too Many Requests.
Without fallbacks?
Your user sees an error.
With fallbacks?
OpenRouter automatically retries using the next available model in your priority list.
Most users won't even notice anything happened.
That's the kind of resilience users expect but rarely appreciate.
Final thoughts
One of the biggest lessons I've learned building AI-powered applications is that your application shouldn't care which model answered the request.
It should care that the request succeeded.
The more your business logic depends on provider-specific behavior, the harder your code becomes to maintain.
OpenRouter flips that relationship.
Providers become interchangeable.
Your application stays consistent.
And as new models continue to emerge almost every week, I think that's an architectural decision that will only become more valuable over time.
If you found this helpful, check out part 1, leave a comment and follow me so you don't miss the next parts of the series, coming soon.
Top comments (0)