연동 문서
OpenAI SDK 와 호환돼요. 바꾸는 건 base URL 과 API 키, 두 가지뿐이에요.
https://router.ethos.ai.kr/v1
인증 Authorization: Bearer ethos_live_…
빠른 시작
발급받은 키(ethos_live_…)를 넣고 base URL 을 바꿔요.
키는 발급하는 순간에 한 번만 보여요. 비밀번호 관리자에 보관하세요.
Python
from openai import OpenAI
client = OpenAI(
base_url="https://router.ethos.ai.kr/v1",
api_key="ethos_live_…",
)
res = client.chat.completions.create(
model="anthropic/claude-sonnet-5",
messages=[{"role": "user", "content": "안녕하세요"}],
max_tokens=1000,
)
print(res.choices[0].message.content)
Node.js
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://router.ethos.ai.kr/v1",
apiKey: process.env.ETHOS_ROUTER_KEY,
});
const res = await client.chat.completions.create({
model: "anthropic/claude-sonnet-5",
messages: [{ role: "user", content: "안녕하세요" }],
});
curl
curl https://router.ethos.ai.kr/v1/chat/completions \
-H "Authorization: Bearer $ETHOS_ROUTER_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "anthropic/claude-sonnet-5",
"messages": [{"role": "user", "content": "안녕하세요"}]}'
모델 지정
모델 코드는 provider/model 형식이에요. 서빙 중인 전체 목록은
GET /v1/models 또는 단가표에서 볼 수 있어요.
| 예시 | 설명 |
|---|---|
anthropic/claude-sonnet-5 | Claude Sonnet 5 |
openai/gpt-5.4 | GPT-5.4 |
google/gemini-3.7-flash | Gemini 3.7 Flash |
upstage/solar-pro-4 | Solar Pro 4 (국산) |
perplexity/sonar-pro | 웹검색이 들어 있어요. 토큰 말고 검색 건당 수수료가 따로 붙어요 (단가표) |
버전 표기는 점(claude-sonnet-5, gpt-5.4)을 써요.
공급사마다 다른 표기는 라우터가 맞춰 줘요.
스트리밍
stream: true 를 그대로 써요. 마지막 usage 청크에 확정 비용이 실려요.
stream = client.chat.completions.create(
model="anthropic/claude-sonnet-5",
messages=[{"role": "user", "content": "안녕하세요"}],
stream=True,
stream_options={"include_usage": True}, # 마지막 청크에 usage.cost
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
if chunk.usage: # 마지막 청크
print("\n비용:", chunk.usage.cost, "원")
stream_options.include_usage 를 안 보내도 라우터가 넣어 줘요.
스트리밍에서도 비용이 빠지지 않아요.
툴 호출 (function calling)
OpenAI 형식 그대로예요. 모델마다 안 맞는 부분(예를 들면 일부 모델의 reasoning_effort 와 툴 동시 사용 제약)은
라우터가 맞춰 주니까 클라이언트에서 따로 처리할 게 없어요.
res = client.chat.completions.create(
model="openai/gpt-5.6-sol",
messages=[{"role": "user", "content": "서울 날씨 알려줘"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {"type": "object",
"properties": {"city": {"type": "string"}}},
},
}],
)
이미지 입력
res = client.chat.completions.create(
model="anthropic/claude-sonnet-5",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "이 이미지를 설명해줘"},
{"type": "image_url",
"image_url": {"url": "data:image/png;base64,…"}},
],
}],
)
프리셋
모델과 파라미터, 시스템 프롬프트를 콘솔에서 묶어 두고 이름으로 불러요. 앱을 다시 배포하지 않고 지침만 바꿀 수 있어요.
res = client.chat.completions.create(
model="preset/sales-bot",
messages=[{"role": "user", "content": "환불 규정 알려줘"}],
)
프리셋의 시스템 프롬프트는 항상 맨 앞에 붙어요. 파라미터는 기본값이라 요청이 이기는데, 콘솔에서 재정의 방지를 켜면 프리셋 값이 이겨요.
키 관리 API
콘솔에서 매니지먼트 키를 발급하면 서빙 키를 코드로 만들고 지울 수 있어요. 고객사마다 키를 따로 내주는 구조라면 이걸로 자동화하세요.
curl -X POST https://router.ethos.ai.kr/manage/v1/keys \
-H "Authorization: Bearer $ETHOS_MANAGE_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"고객사 A","scope":"serve","monthlyLimitKrw":100000}'
매니지먼트 키로는 모델을 부를 수 없고, 매니지먼트 키를 또 만들 수도 없어요. 하나가 새더라도 그 하나만 버리면 되게 만들었어요.
명령줄 도구
키를 손으로 옮기지 않고 붙이는 길이에요. 한 번 로그인하면 코딩 도구가 에토스 라우터를 보게 돼요.
npm i -g ethos-router
ethos login # 브라우저에서 승인하면 키가 저장돼요
ethos claude # Claude Code 를 에토스 라우터로 실행
ethos codex # Codex 를 에토스 라우터로 실행
승인하면 키가 두 개 저장돼요. 모델을 부르는 키와 사용 내역을 읽는 키가
따로예요. 하나가 새더라도 그 키로는 절반만 할 수 있어요.
콘솔의 API 키 목록에 CLI 로 뜨고, 거기서 정지할 수 있어요.
ethos logout 은 설정만 지우는 게 아니라 그 두 키를 정지시켜요.
| 명령 | 하는 일 |
|---|---|
ethos models [검색어] | 쓸 수 있는 모델과 단가 |
ethos chat "질문" | 한 번 물어보고 답과 든 비용을 봐요 |
ethos balance | 남은 크레딧 |
ethos usage --csv 내역.csv | 기간별 사용 내역을 엑셀 파일로 |
ethos env | 다른 도구에 넣을 환경변수를 찍어요 |
ethos doctor | 어디가 막혔는지 점검해요 |
브라우저가 없는 곳(CI)에서는 ethos login --with-key <키> 를 쓰거나
ETHOS_API_KEY 를 넣으세요.
Claude Code · Codex 연동
이 도구들은 OpenAI 형식이 아니라 앤트로픽 Messages 형식으로 말해요.
그래서 창구를 하나 더 열어 뒀어요. 바꾸는 건 여기도 base URL 과 키뿐이에요.
손으로 넣는 대신 명령줄 도구의 ethos claude 를 써도 돼요.
export ANTHROPIC_BASE_URL="https://router.ethos.ai.kr/claude"
export ANTHROPIC_AUTH_TOKEN="ethos_live_…"
claude # 그대로 쓰면 돼요
직접 부를 때는 POST /claude/v1/messages 예요.
요청·응답 모두 앤트로픽 형식이고, stream: true 도 앤트로픽 이벤트로 나가요.
과금과 한도, 사용 기록은 /v1/chat/completions 와 똑같이 걸려요.
응답에 cost 를 하나 더 실어 주는데, 이 요청의 확정 원화 비용이에요.
이미지 생성
POST /v1/images/generations 도 OpenAI 호환이에요. 대화와 같은 키, 같은 원장을 써요.
res = client.images.generate(
model="google/gemini-2.5-flash-image",
prompt="한복 입은 고양이",
)
res.data[0].b64_json # 이미지 바이트(base64)
이미지는 본문에 담아 돌려드려요. 저희가 파일을 보관하지 않아서 response_format="url" 은
받지 않고 오류로 알려드려요. 쓸 수 있는 모델은 단가표의 이미지에 있어요.
출력 토큰에 글자와 이미지가 섞여 오는 모델이라 토큰 단가로는 계산이 맞지 않아요.
공급사가 청구한 실제 금액을 그대로 환산해서 청구하고, 그 값이 응답의 cost 예요.
음성 받아쓰기
POST /v1/audio/transcriptions 도 OpenAI 호환이에요. 파일을 보내면 글자로 돌려드려요.
res = client.audio.transcriptions.create(
model="google/gemini-3.8-flash",
file=open("회의.wav", "rb"),
)
res.text
wav·mp3·m4a·flac·ogg·webm 을 받아요. response_format 은 json 과
text 를 지원하고, 자막(srt·vtt)은 아직 없어요.
고유명사가 많으면 prompt 에 미리 적어 주시면 인식률이 올라가요.
임베딩
POST /v1/embeddings 도 OpenAI 호환이에요. 대화와 같은 키, 같은 원장을 써요.
출력 토큰이 없어서 입력 토큰만 과금해요.
res = client.embeddings.create(
model="openai/text-embedding-3-small",
input=["문장 1", "문장 2"],
)
res.data[0].embedding # [0.01, …]
res.usage.cost # 확정 원화
서빙 중인 임베딩 모델은 GET /v1/models 응답의 kind 가
embedding 인 항목이에요.
비용 필드, usage.cost
모든 응답의 usage 에 이 요청의 확정 원화 비용이 실려요.
단가를 거꾸로 계산할 필요가 없어요.
{
"usage": {
"prompt_tokens": 3000,
"completion_tokens": 500,
"cost": 23.1, // 원 (공급가액, VAT 별도)
"upstream": "anthropic-direct" // 실제 처리 경로
}
}
GET /v1/prices 로 언제든 확인할 수 있어요.
캐시된 입력 토큰은 할인 단가로 계산해요.비용 귀속 (부서·사용자 단위 집계)
요청 헤더로 비용을 어디에 달지 정할 수 있어요. 사용량 API 와 콘솔이 이 단위로 모아서 보여 줘요.
| 헤더 | 용도 |
|---|---|
X-Ethos-Subject | 사용자·부서 식별자 (예: user-1234) |
X-Ethos-Request-Id | 클라이언트 쪽 요청 식별자예요. 사용 기록에 그대로 남아요 |
이 헤더는 키에 허용된 경우에만 반영돼요 (키 발급할 때 정해요).
오류 코드
HTTP 상태와 error.code 로 구분해요. OpenAI SDK 의 예외 체계가 그대로 돌아가요.
| HTTP | code | 의미 · 대응 |
|---|---|---|
| 401 | authentication_error | 키가 없거나 유효하지 않음 (정지·만료·허용되지 않은 위치 포함) |
| 402 | prepaid_credit_exhausted | 워크스페이스 크레딧을 다 썼어요. 충전해야 해요 |
| 402 | credit_limit_exceeded | 일·월 한도 초과 |
| 403 | api_key_model_not_allowed | 이 키로 쓸 수 없는 모델 |
| 404 | model_not_found | 없는 모델 코드 |
| 429 | rate_limit_exceeded | 분당 한도를 넘었어요. Retry-After 헤더(초)만큼 기다렸다 다시 보내세요 |
| 503 | upstream_unavailable | 업스트림 경로가 전부 잠시 안 돼요. 조금 뒤에 다시 보내세요 |
업스트림에 장애가 나면 라우터가 먼저 다음 경로로 넘겨 보고, 그래도 안 되면 그때 503 을 줘요. 400 은 요청 자체의 문제이므로 재시도하지 마세요.
한도
| 축 | 기본 | 변경 |
|---|---|---|
| 분당 요청 (키) | 120 rpm | 키 발급 시 rpmLimit |
| 일·월 크레딧 (키) | 없음 | dailyLimitKrw · monthlyLimitKrw |
| 일·월 크레딧 (워크스페이스) | 없음 | 워크스페이스 설정 |
| IP 허용목록 (키) | 제한 없음 | ipAllow (단일 IP 또는 CIDR) |
모든 요청은 호출하기 전에 잔액을 잡아 둬요. 크레딧이 모자라면 공급사를 부르지도 않고 402 로 거절하니까, 나중에 청구서가 따라오는 일이 없어요.
사용량 API
사용량 조회 전용 키(scope=usage)로 잔액과 사용량, 최근 요청을 봐요.
서빙 키로는 볼 수 없어요. 앱에 박히는 키로 회사 지출까지 읽히면 안 되니까요.
GET /usage/v1/billing # 잔액·이번 달 사용액
GET /usage/v1/usage?group_by=model&days=30 # 모델별 집계
GET /usage/v1/usage?group_by=usage_group&days=30 # 귀속(부서·사용자)별 집계
GET /usage/v1/logs?limit=20&hours=24 # 최근 요청 (실패 건 포함)
GET /usage/v1/logs?from=2026-09-05T00:00:00Z&to=2026-09-06T00:00:00Z # 날짜로 좁혀 보기
GET /usage/v1/logs?limit=50&cursor=… # 다음 쪽 (응답의 next_cursor)
기간은 hours 또는 from·to 로 줘요. 둘 다 주면 from·to 가 이겨요.
페이지는 응답의 next_cursor 를 cursor 로 다시 보내서 넘겨요. 로그는 보는 동안에도
위에 쌓이는데, 커서는 "이 지점보다 과거"라 이미 넘긴 쪽이 흔들리지 않아요.
같은 내용을 콘솔에서 화면으로도 볼 수 있어요.
키 발급
지금은 영업 문의로 워크스페이스와 키를 만들어 드려요.
문의: router@ethos.ai.kr