Split View: OpenRouter 사용법 — 하나의 API로 300개 이상 LLM 쓰기
OpenRouter 사용법 — 하나의 API로 300개 이상 LLM 쓰기
- 개요
- 1. 왜 게이트웨이 계층이 필요한가
- 2. 핵심 개념
- 3. 키 발급과 첫 curl 요청
- 4. OpenAI SDK 드롭인
- 5. 모델 선택과 라우팅
- 6. 스트리밍 / 툴 호출 / 구조화 출력
- 7. 어트리뷰션 헤더와 사용량 확인
- 8. 실무 팁과 주의점
- 참고 자료
개요
여러 LLM을 다루기 시작하면 금세 같은 문제에 부딪힌다. OpenAI, Anthropic, Google, Meta의 모델을 섞어 쓰고 싶은데 각 공급자마다 별도의 SDK, 별도의 인증, 별도의 결제, 별도의 요청 스키마가 있다. 모델을 하나 바꾸려면 클라이언트 코드를 다시 짜야 하고, 공급자 하나가 다운되면 서비스가 멈춘다.
OpenRouter는 이 지점을 정리한다. 하나의 엔드포인트와 하나의 API 키로 60개 이상 공급자의 300개 이상(최대 약 500개) 모델에 접근할 수 있고, 인터페이스는 OpenAI API와 완전히 호환된다. 즉 이미 OpenAI SDK를 쓰고 있다면 base_url과 api_key만 바꿔서 그대로 Claude, Gemini, Llama를 호출할 수 있다.
이 글은 OpenRouter를 왜 게이트웨이로 쓰는지, 핵심 개념은 무엇인지, 그리고 키 발급부터 라우팅/폴백/스트리밍까지 실제로 어떻게 붙이는지를 단계별로 다룬다.
1. 왜 게이트웨이 계층이 필요한가
LLM 애플리케이션은 처음에는 단일 모델로 충분해 보인다. 하지만 운영에 들어가면 공통 요구가 생긴다.
- 작업마다 최적 모델이 다르다. 요약은 저렴한 모델로, 복잡한 추론은 프런티어 모델로 나누고 싶다.
- 한 공급자에 종속되고 싶지 않다. 가격이 오르거나 장애가 나면 다른 모델로 옮겨야 한다.
- 결제와 사용량을 한곳에서 보고 싶다. 공급자마다 대시보드를 따로 여는 것은 비효율적이다.
게이트웨이는 이 문제를 애플리케이션 코드 밖으로 끌어낸다. 클라이언트는 항상 같은 엔드포인트에 요청하고, 모델을 바꾸는 것은 문자열 하나(model 필드)를 교체하는 일이 된다. 공급자 라우팅, 폴백, 과금 집계는 게이트웨이가 처리한다.
| 직접 호출 방식 | OpenRouter 게이트웨이 |
|---|---|
| 공급자마다 SDK/인증/스키마가 다름 | 하나의 엔드포인트, 하나의 키, OpenAI 호환 스키마 |
| 모델 교체 시 코드 변경 | model 문자열만 교체 |
| 공급자별 결제 계정 | 크레딧 하나로 통합 과금 |
| 장애 시 직접 폴백 구현 | models 배열/provider 라우팅으로 자동 폴백 |
2. 핵심 개념
하나의 엔드포인트
모든 호출은 하나의 베이스 URL로 간다.
https://openrouter.ai/api/v1
채팅 완성(chat completions)은 https://openrouter.ai/api/v1/chat/completions로 요청한다. 인증은 표준 Bearer 토큰 헤더를 쓴다.
Authorization: Bearer YOUR_OPENROUTER_API_KEY
모델 ID
모델은 provider/model 형식의 문자열로 지정한다. 대표적인 예는 다음과 같다.
| 모델 ID | 설명 |
|---|---|
openai/gpt-4o | OpenAI GPT-4o |
anthropic/claude-3.5-sonnet | Anthropic Claude 3.5 Sonnet |
google/gemini-2.0-flash-exp | Google Gemini 2.0 Flash (실험) |
meta-llama/llama-3.3-70b-instruct | Meta Llama 3.3 70B Instruct |
openrouter/auto | 자동 라우터 — OpenRouter가 요청에 맞는 모델을 선택 |
무료 변형은 ID 끝에 :free가 붙는다. 전체 목록과 각 모델의 가격/컨텍스트 길이는 모델 페이지에서 확인할 수 있고, 프로그램적으로는 /api/v1/models 엔드포인트로 조회한다.
크레딧과 무료 티어
무료 티어는 하루 약 50건의 무료 요청과 25개 이상의 무료 모델(ID가 :free로 끝나는)을 제공한다. 유료 사용은 크레딧 기반의 종량제이며, 실제로 실행된 모델의 토큰 단위로 과금된다. 크레딧을 충전할 때 약 5.5%의 소액 수수료가 붙지만, 호출 자체의 비용은 그 요청을 실제로 처리한 모델의 단가를 따른다.
3. 키 발급과 첫 curl 요청
먼저 openrouter.ai/keys에서 API 키를 발급한다. 키를 환경 변수에 넣어 두면 편하다.
export OPENROUTER_API_KEY="sk-or-여기에-발급받은-키"
이제 가장 단순한 요청을 curl로 보내 본다.
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "model": "openai/gpt-4o", "messages": [{"role":"user","content":"Hello"}] }'
응답은 OpenAI의 chat completions와 동일한 구조로 돌아온다. 모델을 바꾸고 싶으면 model 값만 anthropic/claude-3.5-sonnet이나 google/gemini-2.0-flash-exp로 교체하면 된다. 나머지 요청 형태는 그대로다.
4. OpenAI SDK 드롭인
OpenRouter가 OpenAI 호환이라는 말의 실질적 의미는, 공식 OpenAI SDK를 그대로 쓰되 접속 지점만 OpenRouter로 돌린다는 것이다.
Python
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key="YOUR_OPENROUTER_API_KEY",
)
completion = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[
{"role": "user", "content": "게이트웨이가 왜 유용한지 한 문장으로 설명해줘"},
],
)
print(completion.choices[0].message.content)
OpenAI(...) 생성자에서 base_url과 api_key만 OpenRouter 값으로 바꾸면, 이후 client.chat.completions.create(...) 호출 방식은 평소와 완전히 같다. 응답 본문은 completion.choices[0].message.content로 읽는다.
TypeScript
TypeScript도 같은 아이디어다. openai npm 클라이언트에 baseURL과 apiKey만 지정한다.
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY,
});
const completion = await client.chat.completions.create({
model: 'anthropic/claude-3.5-sonnet',
messages: [{ role: 'user', content: 'Hello' }],
});
console.log(completion.choices[0].message.content);
SDK를 쓰지 않고 순수 fetch로 POST 해도 된다. 엔드포인트와 헤더만 맞추면 동일하게 동작한다.
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'openai/gpt-4o',
messages: [{ role: 'user', content: 'Hello' }],
}),
});
const data = await res.json();
console.log(data.choices[0].message.content);
5. 모델 선택과 라우팅
공급자 라우팅 오버라이드
같은 모델을 여러 공급자가 제공하는 경우가 있다. 이때 OpenRouter는 기본적으로 가격, 지연시간, 가용성을 기준으로 공급자를 자동 선택한다. 특정 정책을 강제하고 싶으면 요청에 provider 필드를 넣어 오버라이드한다. 예를 들어 공급자 우선순위(order), 폴백 허용 여부(allow_fallbacks), 정렬 기준(sort) 등을 지정할 수 있다.
{
"model": "meta-llama/llama-3.3-70b-instruct",
"messages": [{ "role": "user", "content": "Hello" }],
"provider": {
"sort": "throughput",
"allow_fallbacks": true
}
}
모델 레벨 폴백 — models 배열
공급자 라우팅과 별개로, 여러 모델을 순서대로 시도하는 폴백도 지정할 수 있다. models 배열을 넘기면 앞에서부터 차례로 시도한다.
{
"models": [
"openai/gpt-4o",
"anthropic/claude-3.5-sonnet",
"meta-llama/llama-3.3-70b-instruct"
],
"messages": [{ "role": "user", "content": "Hello" }]
}
폴백은 레이트 리밋, 다운타임, 컨텍스트 길이 초과, 모더레이션 오류 같은 상황에서 트리거된다. 중요한 점은 과금 방식이다. 여러 모델을 나열해도 실제로 실행되어 응답을 만든 모델의 토큰에 대해서만 과금된다. 시도했다가 실패한 모델에는 비용이 붙지 않는다.
자동 라우터 — openrouter/auto
모델을 직접 고르기 애매할 때는 openrouter/auto를 model 값으로 지정하면 된다. OpenRouter가 요청 내용을 보고 적절한 모델로 라우팅한다. 초기 프로토타이핑이나 "일단 잘 되는 모델"이 필요한 단계에서 유용하다.
6. 스트리밍 / 툴 호출 / 구조화 출력
이 세 가지 기능은 모두 OpenAI API와 같은 방식으로 동작한다.
스트리밍
stream=True(TypeScript는 stream: true)를 넘기면 응답을 청크 단위로 받는다.
stream = client.chat.completions.create(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "짧은 시 하나 써줘"}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="")
툴/함수 호출
툴(함수) 호출도 OpenAI 스키마 그대로다. tools 배열에 함수 정의를 넣고, 모델이 반환한 tool_calls를 처리한 뒤 결과를 다시 메시지로 넣어 준다.
{
"model": "openai/gpt-4o",
"messages": [{ "role": "user", "content": "서울 날씨 알려줘" }],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "도시의 현재 날씨를 반환한다",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
}
]
}
구조화 출력
JSON 스키마 기반 구조화 출력도 지원한다. response_format에 json_schema를 지정하면 응답이 해당 스키마를 따르도록 강제할 수 있다.
{
"model": "openai/gpt-4o",
"messages": [{ "role": "user", "content": "샘플 사용자 하나 만들어줘" }],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "user",
"schema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer" }
},
"required": ["name", "age"]
}
}
}
}
7. 어트리뷰션 헤더와 사용량 확인
OpenRouter는 openrouter.ai의 리더보드(어떤 앱이 어떤 모델을 많이 쓰는지)를 위한 선택적 어트리뷰션 헤더를 지원한다. 필수는 아니며, 넣으면 자기 앱을 리더보드에 노출할 수 있다.
HTTP-Referer— 자기 사이트 URLX-Title— 자기 앱 이름
OpenAI SDK에서는 이 헤더를 기본 헤더로 넘긴다. Python은 default_headers, JavaScript는 defaultHeaders를 쓴다.
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key="YOUR_OPENROUTER_API_KEY",
default_headers={
"HTTP-Referer": "https://your-site.example",
"X-Title": "My App",
},
)
const client = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY,
defaultHeaders: {
'HTTP-Referer': 'https://your-site.example',
'X-Title': 'My App',
},
});
사용 가능한 모델 목록과 메타데이터는 /api/v1/models 엔드포인트로 조회한다.
curl https://openrouter.ai/api/v1/models \
-H "Authorization: Bearer $OPENROUTER_API_KEY"
8. 실무 팁과 주의점
- 비용은 항상 실제로 실행된 하위 모델의 단가를 따른다.
openrouter/auto나models폴백을 쓸 때는 어떤 모델이 실행될지에 따라 단가가 달라진다는 점을 염두에 둔다. :free모델은 무료지만 레이트 리밋이 낮다. 프로덕션 트래픽을 무료 모델에만 의존하면 금방 한도에 걸린다. 프로토타이핑이나 저부하 용도로 쓰는 것이 안전하다.- API 키는 반드시 서버 사이드에 둔다. 브라우저 번들이나 클라이언트 코드에 키를 노출하면 그대로 도난당한다. 프런트엔드에서 호출해야 한다면 자체 백엔드를 프록시로 두고 그 뒤에서 OpenRouter를 호출한다.
- 모델을 바꿔도 응답 스키마가 OpenAI 호환으로 유지되므로, 파싱 코드를 모델마다 다시 짤 필요는 없다. 다만 모델별로 툴 호출이나 구조화 출력 지원 수준이 다를 수 있으니, 새 모델로 바꿀 때는 해당 기능을 실제로 확인한다.
- 폴백 배열은 안정성을 높이지만, 뒤쪽에 비싼 모델을 두면 장애 시 비용이 튈 수 있다. 폴백 순서는 가용성뿐 아니라 단가도 고려해서 배치한다.
참고 자료
How to Use OpenRouter — 300+ LLMs Through One API
- Overview
- 1. Why You Need a Gateway Layer
- 2. Core Concepts
- 3. Getting a Key and Your First curl Request
- 4. OpenAI SDK Drop-In
- 5. Choosing Models and Routing
- 6. Streaming / Tool Calling / Structured Output
- 7. Attribution Headers and Checking Usage
- 8. Practical Tips and Caveats
- References
Overview
Once you start working with more than one LLM, you hit the same problem quickly. You want to mix models from OpenAI, Anthropic, Google, and Meta, but each provider ships its own SDK, its own authentication, its own billing, and its own request schema. Swapping a model means rewriting client code, and if one provider goes down, your service goes down with it.
OpenRouter cleans this up. A single endpoint and a single API key give you access to 300+ (up to roughly 500) models from 60+ providers, and the interface is fully compatible with the OpenAI API. So if you are already using the OpenAI SDK, you can call Claude, Gemini, or Llama just by changing base_url and api_key.
This guide covers why you would use OpenRouter as a gateway, what the core concepts are, and how to actually wire it up — step by step from getting a key through routing, fallbacks, and streaming.
1. Why You Need a Gateway Layer
An LLM application looks fine with a single model at first. But once it goes to production, common needs appear.
- Different tasks want different models. You want cheap models for summarization and frontier models for hard reasoning.
- You do not want to be locked into one provider. If prices rise or an outage hits, you need to move to another model.
- You want billing and usage in one place. Opening a separate dashboard per provider is inefficient.
A gateway pulls this out of your application code. The client always talks to the same endpoint, and switching models becomes a matter of swapping a single string (the model field). Provider routing, fallbacks, and billing aggregation are handled by the gateway.
| Direct-call approach | OpenRouter gateway |
|---|---|
| Different SDK/auth/schema per provider | One endpoint, one key, OpenAI-compatible schema |
| Code changes to swap a model | Swap only the model string |
| Separate billing account per provider | Unified billing on one credit balance |
| Roll your own fallback on failure | Automatic fallback via models array / provider routing |
2. Core Concepts
One Endpoint
Every call goes to a single base URL.
https://openrouter.ai/api/v1
Chat completions are requested at https://openrouter.ai/api/v1/chat/completions. Authentication uses a standard Bearer token header.
Authorization: Bearer YOUR_OPENROUTER_API_KEY
Model IDs
Models are specified as strings in the provider/model form. Common examples:
| Model ID | Description |
|---|---|
openai/gpt-4o | OpenAI GPT-4o |
anthropic/claude-3.5-sonnet | Anthropic Claude 3.5 Sonnet |
google/gemini-2.0-flash-exp | Google Gemini 2.0 Flash (experimental) |
meta-llama/llama-3.3-70b-instruct | Meta Llama 3.3 70B Instruct |
openrouter/auto | Auto-router — OpenRouter picks a model for the request |
Free variants end in :free. The full catalog with per-model pricing and context length is on the models page, and programmatically you can query the /api/v1/models endpoint.
Credits and Free Tier
The free tier gives you roughly 50 free requests per day and 25+ free models (whose IDs end in :free). Paid usage is credit-based and pay-as-you-go, billed per token of the model that actually runs. When you top up credits, there is a small fee (about 5.5%), but the cost of a call itself follows the per-token price of the model that actually served the request.
3. Getting a Key and Your First curl Request
First, create an API key at openrouter.ai/keys. It is convenient to put it in an environment variable.
export OPENROUTER_API_KEY="sk-or-your-key-here"
Now send the simplest possible request with curl.
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "model": "openai/gpt-4o", "messages": [{"role":"user","content":"Hello"}] }'
The response comes back in the same shape as OpenAI's chat completions. To switch models, change only the model value to anthropic/claude-3.5-sonnet or google/gemini-2.0-flash-exp. The rest of the request stays the same.
4. OpenAI SDK Drop-In
The practical meaning of "OpenRouter is OpenAI-compatible" is that you keep using the official OpenAI SDK and just point it at OpenRouter.
Python
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key="YOUR_OPENROUTER_API_KEY",
)
completion = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[
{"role": "user", "content": "Explain in one sentence why a gateway is useful"},
],
)
print(completion.choices[0].message.content)
In the OpenAI(...) constructor, you only change base_url and api_key to the OpenRouter values; after that, calling client.chat.completions.create(...) works exactly as usual. Read the response body from completion.choices[0].message.content.
TypeScript
TypeScript is the same idea. Give the openai npm client a baseURL and an apiKey.
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY,
});
const completion = await client.chat.completions.create({
model: 'anthropic/claude-3.5-sonnet',
messages: [{ role: 'user', content: 'Hello' }],
});
console.log(completion.choices[0].message.content);
You can also skip the SDK and POST with plain fetch. As long as the endpoint and headers match, it behaves identically.
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'openai/gpt-4o',
messages: [{ role: 'user', content: 'Hello' }],
}),
});
const data = await res.json();
console.log(data.choices[0].message.content);
5. Choosing Models and Routing
Provider Routing Override
Some models are offered by several providers. In that case OpenRouter auto-picks a provider based on price, latency, and availability. To force a specific policy, add a provider field to the request to override it. For example, you can specify provider priority (order), whether fallbacks are allowed (allow_fallbacks), and the sort criterion (sort).
{
"model": "meta-llama/llama-3.3-70b-instruct",
"messages": [{ "role": "user", "content": "Hello" }],
"provider": {
"sort": "throughput",
"allow_fallbacks": true
}
}
Model-Level Fallback — the models Array
Separately from provider routing, you can specify a fallback that tries several models in order. Pass a models array and it tries them from the front.
{
"models": [
"openai/gpt-4o",
"anthropic/claude-3.5-sonnet",
"meta-llama/llama-3.3-70b-instruct"
],
"messages": [{ "role": "user", "content": "Hello" }]
}
Fallbacks trigger on rate limits, downtime, context-length overflow, or moderation errors. The important part is billing. Even if you list several models, you are billed only for the tokens of the model that actually ran and produced the response. Models that were tried and failed cost nothing.
Auto-Router — openrouter/auto
When picking a model yourself is awkward, set the model value to openrouter/auto. OpenRouter looks at the request and routes it to a suitable model. This is useful for early prototyping or whenever you just need "a model that works."
6. Streaming / Tool Calling / Structured Output
All three of these features work the same way as in the OpenAI API.
Streaming
Pass stream=True (or stream: true in TypeScript) to receive the response in chunks.
stream = client.chat.completions.create(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "Write a short poem"}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="")
Tool / Function Calling
Tool (function) calling uses the OpenAI schema as-is. Put function definitions in a tools array, handle the tool_calls the model returns, then feed the results back as messages.
{
"model": "openai/gpt-4o",
"messages": [{ "role": "user", "content": "What is the weather in Seoul?" }],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Return the current weather for a city",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
}
]
}
Structured Output
JSON-schema-based structured output is also supported. Set json_schema in response_format to force the response to conform to that schema.
{
"model": "openai/gpt-4o",
"messages": [{ "role": "user", "content": "Make one sample user" }],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "user",
"schema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer" }
},
"required": ["name", "age"]
}
}
}
}
7. Attribution Headers and Checking Usage
OpenRouter supports optional attribution headers for the leaderboards on openrouter.ai (which apps use which models most). They are not required; adding them lets your app show up on the leaderboards.
HTTP-Referer— your site URLX-Title— your app name
In the OpenAI SDK you pass these as default headers. Python uses default_headers, JavaScript uses defaultHeaders.
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key="YOUR_OPENROUTER_API_KEY",
default_headers={
"HTTP-Referer": "https://your-site.example",
"X-Title": "My App",
},
)
const client = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY,
defaultHeaders: {
'HTTP-Referer': 'https://your-site.example',
'X-Title': 'My App',
},
});
You can list available models and their metadata via the /api/v1/models endpoint.
curl https://openrouter.ai/api/v1/models \
-H "Authorization: Bearer $OPENROUTER_API_KEY"
8. Practical Tips and Caveats
- Cost always follows the per-token price of the underlying model that actually ran. When you use
openrouter/autoor amodelsfallback, remember that the price depends on which model ends up running. :freemodels are free but have low rate limits. If you lean on free models for production traffic, you will hit the limits quickly. It is safer to use them for prototyping or low-load work.- Keep your API key server-side. If you expose the key in a browser bundle or client code, it will be stolen outright. If you must call from the frontend, put your own backend in front as a proxy and call OpenRouter behind it.
- Because the response schema stays OpenAI-compatible across models, you do not need to rewrite parsing code per model. That said, support for tool calling or structured output can differ by model, so when you switch to a new model, verify that the feature actually works.
- Fallback arrays improve reliability, but putting an expensive model at the tail can spike your cost during an outage. Order your fallbacks with unit price in mind, not just availability.