Как перейти с прямого Anthropic/OpenAI API на совместимый шлюз, не переписывая код
Короткий ответ: меняются две вещи — base_url и ключ. Всё остальное (структура запроса, SSE-стрим, tool calls) остаётся прежним, потому что совместимость здесь не «похожесть», а один и тот же протокол. Ниже — что именно совпадает, что не совпадает, где ломается Claude Code и как посчитать, сколько это экономит.
Какие три стены реально останавливают разработчика
Стен ровно три, и они независимы друг от друга. Когда разработчик ищет аналог Anthropic API, он в 9 случаях из 10 ищет не другую модель, а другой способ до неё дотянуться.
Почему Claude API не принимает карту
Биллинг Anthropic и OpenAI требует карты, выпущенной в поддерживаемой юрисдикции. Ситуация «Claude API не принимает карту» — это не баг и не временный сбой, а отказ на стороне платёжного процессинга. Перевыпуск карты, смена банка и повторные попытки её не лечат: проверяется страна эмитента.
Что означает region not supported в Claude
Даже с оплаченным аккаунтом запрос из неподдерживаемого региона получает region not supported. Claude отвечает не ошибкой модели, а отказом на уровне edge — до инференса дело не доходит. VPN формально решает задачу, но добавляет второй источник отказов: смена IP-адресов регулярно приводит к блокировке аккаунта. Для продакшена это неприемлемая архитектура: зависимость от компонента, который никто не поддерживает.
Почему прямой тариф дорог именно для кодинг-агента
Официальный тариф Claude Opus 5 — $5.00 за 1M входных токенов и $25.00 за 1M выходных. Для агента, который на каждом шаге перечитывает контекст репозитория, это набегает быстро. Причём основная строка счёта — не input и не output, а чтение кэша, о чём ниже будет отдельный расчёт с арифметикой.
Шлюз с совместимым протоколом закрывает все три стены сразу: платёж проходит рублями через СБП или криптовалютой, запрос идёт на домен без региональных ограничений — то есть Anthropic-совместимый API доступен из России и не требует зарубежной карты, — а цена по каждой модели опубликована рядом с официальным тарифом на странице цен.
Что значит «совместимый API» на уровне протокола
Слово «совместимый» в маркетинге означает что угодно. OpenAI-совместимый API и Anthropic-совместимый API — конкретное техническое утверждение: сервер принимает те же тела запросов и отдаёт те же тела ответов, что и вендорский. Проверяется оно этим списком.
Что совпадает дословно
| Что | Anthropic-протокол | OpenAI-протокол |
|---|---|---|
| Endpoint | POST /v1/messages | POST /v1/chat/completions |
| Авторизация | заголовок x-api-key | Authorization: Bearer … |
| Поля запроса | model, max_tokens, messages | model, max_tokens, messages |
| Стриминг | SSE | SSE (stream: true) |
| Tool calls | поддерживаются | поддерживаются |
| Стоимость запроса | usage.cost_usd | usage.cost_usd |
То есть SDK от вендора работает как есть: anthropic для Python/JS и openai для Python/TS не различают, куда именно они стучатся, если сервер отвечает тем же контрактом. Никаких форков SDK, никаких прокси-обёрток, никакого переписывания слоя работы с LLM.
Дополнительно шлюз возвращает usage.cost_usd — фактическую стоимость запроса в долларах, в том числе в финальном usage-блоке SSE-стрима. Такого поля у вендоров нет, оно удобно для сверки биллинга прямо из кода.
Что не совпадает — список закрытый
Опубликованы три endpoint'а с ключом: /v1/messages, /v1/chat/completions, /v1/images/generations. И три без ключа: /status, /v1/models, /api/pricing.
Не опубликованы: Responses API (/v1/responses), embeddings, image edits, video, audio. Если ваш код использует client.responses.create(...) или client.embeddings.create(...) — это не мигрирует сменой base_url, это переписывается или остаётся у прежнего провайдера.
Media на вход не принимается. Изображения, PDF и любой бинарь в текстовых endpoint'ах дают 400 unsupported_content_type. Генерация изображений работает (/v1/images/generations), а вот vision-сценарий «отправить скриншот и спросить, что на нём» — нет. Это частая неожиданная поломка: мультимодальный вход легко спрятан внутри одной функции.
Как сменить base_url в Anthropic SDK
Один аргумент конструктора. Ниже диффы для Python и JS.
Python
Было (прямой вызов Anthropic):
from anthropic import Anthropic
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
Стало:
import os
from anthropic import Anthropic
client = Anthropic(
base_url="https://go2llm.tech",
api_key=os.environ["GO2LLM_API_KEY"],
)
msg = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Привет"}],
)
print(msg.usage) # у шлюза там ещё cost_usd
Обратите внимание на две детали. Первая: для Anthropic-протокола base URL указывается без /v1 — SDK добавит путь сам. Вторая: ключ шлюза имеет префикс gk_sk_…, он не взаимозаменяем с ключом вендора.
JavaScript / TypeScript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: "YOUR_API_KEY",
baseURL: "https://go2llm.tech",
});
const response = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 120,
messages: [{ role: "user", content: "Скажи OK" }],
});
console.log(response.content);
Проверка одним curl
Прежде чем трогать приложение, проверьте ключ:
curl https://go2llm.tech/v1/messages \
-H "x-api-key: YOUR_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"claude-sonnet-5","max_tokens":120,"messages":[{"role":"user","content":"Ответь одним словом: работает"}]}'
Заголовок anthropic-version: 2023-06-01 сохраняется — это часть протокола, а не привязка к вендору.
Как выглядит миграция с OpenAI API
Здесь base_url наоборот с /v1: https://go2llm.tech/v1. Путается это постоянно, поэтому запомните правило — Anthropic SDK сам дописывает /v1, OpenAI SDK ждёт его в base URL.
Python
from openai import OpenAI
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://go2llm.tech/v1",
)
response = client.chat.completions.create(
model="gpt-5.6-sol",
messages=[{"role": "user", "content": "Ответь: OK"}],
)
print(response.choices[0].message.content)
TypeScript со стримом
Стриминг — обычная точка, где «совместимый api» ломается у самодельных прокси. Здесь это тот же SSE, итератор SDK работает без изменений:
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://go2llm.tech/v1",
apiKey: process.env.GO2LLM_API_KEY,
});
const stream = await client.chat.completions.create({
model: "gpt-5.6-sol",
stream: true,
messages: [{ role: "user", content: "Сгенерируй SQL" }],
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
Одна важная оговорка про надёжность стрима: до начала ответа шлюз может использовать совместимый fallback-маршрут на разрешённых классах ошибок, но после первого байта или валидного SSE-события переключение не выполняется. Поток нельзя автоматически повторить после получения первого валидного фрагмента — ретрай стрима должен быть решением вашего приложения, а не фоновой магией.
Как перевести Claude Code на другой endpoint
Три переменные окружения, никаких конфигов:
export ANTHROPIC_BASE_URL=https://go2llm.tech
export ANTHROPIC_AUTH_TOKEN=YOUR_API_KEY
export ANTHROPIC_MODEL=claude-sonnet-5
claude
Почему ANTHROPIC_AUTH_TOKEN, а не ANTHROPIC_API_KEY — и откуда берётся 401
Это главная ловушка миграции, и её почти никто не документирует.
Claude Code умеет аутентифицироваться двумя разными способами: по сохранённому логину (вы когда-то залогинились через claude.ai или Anthropic Console — креды лежат в профиле на машине) и по переданному токену. Эти два пути имеют разный приоритет.
Если на машине уже есть вход в Claude Code под личным аккаунтом,ANTHROPIC_API_KEYможет уступить сохранённому логину, и запрос уйдёт мимо go2llm с401.
Симптом выглядит абсурдно: вы выставили ANTHROPIC_BASE_URL, экспортировали ключ в ANTHROPIC_API_KEY, curl с тем же ключом отрабатывает отлично, а Claude Code возвращает 401. Причина в том, что клиент подставил не ваш ключ, а сохранённые OAuth-креды личного аккаунта — которые на стороннем base URL, разумеется, невалидны.
ANTHROPIC_AUTH_TOKEN ведёт себя иначе: он всегда обращается к указанному ANTHROPIC_BASE_URL с переданным значением. Сохранённый логин в эту цепочку не вмешивается.
Практический вывод:
- Для стороннего base URL используйте
ANTHROPIC_AUTH_TOKEN. Всегда. ANTHROPIC_API_KEYоставьте пустым или не экспортируйте вовсе — одновременная установка обеих переменных создаёт ровно ту неоднозначность, из-за которой возникает 401.- Если 401 остался — проверьте ключ тем же
curlиз раздела выше. Если curl проходит, а клиент нет, дело в клиенте, а не в ключе.
Что с другими инструментами
| Инструмент | Протокол | Статус |
|---|---|---|
| Claude Code | Anthropic Messages | ✅ проверено живым запросом |
| OpenCode | OpenAI Chat Completions | ✅ проверено живым запросом |
| Kilo Code | OpenAI Chat Completions | 📄 по документации, вручную не тестировали |
| Cline / RooCode / Continue.dev / Aider | OpenAI Chat Completions | 📄 работает, если клиент умеет свой base URL |
| Codex CLI | OpenAI Responses | ✅ проверено живым запросом 2026-09-14: codex exec 0.151.0, модель qwen3-8-flash |
| Cursor | — | 📄 живым запросом не проверяли |
Общее правило: если клиент умеет свой base URL и говорит на одном из двух протоколов — он подключается. Если не умеет — нет, и настройками это не лечится.
Чек-лист миграции прода
Девять пунктов. Проходить до переключения трафика, а не после.
- Возьмите model ID из
/v1/models, а не из памяти. Идентификаторы моделей в каталоге шлюза — свои (claude-opus-5,gpt-5.6-sol,gemini-3.8-flash). Ошибка в строке даёт404 model_not_found. Каталог открыт без ключа:
``bash curl https://go2llm.tech/v1/models ``
- Сверьте endpoint с протоколом модели. В ответе
/v1/modelsдля каждой модели указаны модальности и endpoints. Вызов модели не по её протоколу возвращает400 model_protocol_mismatch. Не угадывайте — читайте каталог. - Проверьте, что в запросах нет media на вход. Любое изображение, аудио, видео или PDF в текстовом endpoint'е —
400 unsupported_content_type. Пройдитесь грепом по местам, где собираетсяcontent-массив. - Добавьте обработку
402 insufficient_balance. Это новый для вас класс ошибок: у вендора с привязанной картой его не было. Реакция — пополнить баланс или уменьшитьmax_tokens. Баланс не уходит ниже нуля, поэтому 402 — штатное состояние, а не авария. - Реализуйте backoff по
429с учётомRetry-After. Дефолтный лимит на ключ — 20 RPM. Если ваш пайплайн шлёт параллельные запросы пачками, вы упрётесь в него на первом же прогоне. Слепой ретрай без задержки только усугубит ситуацию. - Отделите
502 upstream_unavailableот логических ошибок. Повторять с задержкой безопасно — но только безопасный текстовый запрос. Media-запрос сambiguous_upstream_resultповторять автоматически нельзя: поставщик мог его принять, и повтор спишет деньги дважды. - Выставьте лимит ключа. На каждый ключ настраиваются квота в долларах, список разрешённых моделей, IP allow-list и срок действия. Ключ, созданный в кабинете с пустым полем квоты, идёт без отдельного лимита — для прода это выставляется вручную в кабинете. Ключ с квотой
0означает «без отдельного лимита», расход ограничен только балансом. - Логируйте
usage.cost_usd. Шлюз резервирует оценочную стоимость до вызова, затем списывает фактическую и освобождает остаток резерва. Сверка поcost_usdиз ответа даёт вам собственную картину расходов, независимую от кабинета. - Если используете генерацию изображений — добавьте
Idempotency-Key. Для image-запросов он обязателен: 8–128 печатных ASCII, тело до 20 MiB,response_format: "url". Переиспользование одного ключа для разных тел даёт409 idempotency_conflict.
Какие ошибки вы увидите и что каждая означает
Формат ошибки единый: error.type, error.code, error.message, error.request_id. Последнее поле сохраняйте в логи — по нему разбирают обращения.
| HTTP | Код | Что произошло на самом деле |
|---|---|---|
| 400 | invalid_request | Тело запроса не проходит валидацию: битый JSON или нет обязательного поля. Проверяется без обращения к модели. |
| 400 | unsupported_content_type | В текстовый endpoint попало изображение/аудио/видео/PDF. Уберите media из запроса. |
| 400 | model_protocol_mismatch | Модель вызвана не тем endpoint'ом. Возьмите правильный из /v1/models. |
| 401 | no-key | Ключ не дошёл до сервера. В Claude Code — почти всегда та самая история с ANTHROPIC_API_KEY вместо ANTHROPIC_AUTH_TOKEN. |
| 402 | insufficient_balance | На балансе меньше, чем резерв под запрос. Пополнить или снизить max_tokens. |
| 403 | model / inactive / expired / ip | Ключ валиден, но настройки запрещают вызов: модель не в списке, ключ отключён, истёк срок или IP вне allow-list. |
| 404 | model_not_found | Опечатка в model ID или устаревший каталог. Скопируйте точную строку из /v1/models. |
| 409 | idempotency_conflict | Один Idempotency-Key отправлен с разными телами запроса. |
| 429 | quota / rate_limit | Исчерпана квота ключа либо превышен RPM. Читайте Retry-After. |
| 502 | upstream_unavailable | Проблема у внешнего поставщика. Безопасный текстовый запрос можно повторить с задержкой. |
| 503 | product_paused / no_eligible_route | Нет доступного маршрута или продукт на паузе. Смотрите /status, повторяйте позже. |
Отдельный случай: 404 на /v1/responses — это не проблема ключа, а несовместимый протокол клиента. Подробный разбор кодов — в документации по ошибкам.
Чего в шлюзе нет — честно
Список короткий и закрытый. Если ваш сценарий попал сюда, сменой base_url вы не отделаетесь.
Responses API. Endpoint /v1/responses не опубликован. Код на client.responses.create() не заработает.
Embeddings. Нет. Векторизацию оставляйте у текущего провайдера или на локальной модели.
Media на вход. Есть генерация изображений, нет их разбора. Vision-пайплайн не мигрирует.
Fine-tuning. Нет. Шлюз отдаёт опубликованный каталог моделей, дообучение в него не входит.
Codex CLI. Подключается с 2026-09-14. Актуальные версии Codex CLI для кастомных провайдеров (model_providers в ~/.codex/config.toml) поддерживают только wire_api = "responses" — формат OpenAI Responses API; значение wire_api = "chat" сам Codex CLI отклоняет с ошибкой «no longer supported». Раньше маршрута /v1/responses у шлюза не было и Codex получал 404. Теперь он опубликован и проверен живым прогоном Codex CLI 0.151.0 на модели qwen3-8-flash: codex exec прочитал файл, исполнил инструменты и внёс правку. Интерактивный TUI codex мы не гоняли. Разбор целиком — на странице про Codex.
Ещё одно, что стоит знать заранее: интеграции за клиента не настраиваются. Документация есть, руки — ваши.
Сколько это экономит: считаем на профиле кодинг-агента
Сравнивать провайдеров по цене input — ошибка: у кодинг-агента распределение токенов другое. Реальный профиль нагрузки — вход 10%, выход 5%, чтение кэша 85%: агент на каждом шаге перечитывает тот же контекст репозитория, и тот приходит из кэша. Смешанная цена считается так:
mixed = input × 0.10 + output × 0.05 + cache_read × 0.85
Считаем обе стороны по одной формуле — иначе сравнение нечестное.
Claude Opus 5: две смешанные цены, шаг за шагом
Официальный тариф: input $5.00, output $25.00, cache read $0.50 за 1M токенов.
input: 5.00 × 0.10 = 0.500
output: 25.00 × 0.05 = 1.250
cache read: 0.50 × 0.85 = 0.425
mixed = 2.175 $ / 1M токенов
На 100M токенов смешанного трафика это $217.50 по официальному тарифу. По курсу ЦБ 86,3793 ₽ за $1 (01.09.2026) — примерно 18 787 ₽. Курс справочный: баланс и списания ведутся в USD.
Чтобы получить вторую половину сравнения, подставьте в ту же формулу три ставки шлюза со страницы цен. Смотреть надо именно на все три: экономия кодинг-агента решается строкой cache read, на которую приходится 85% объёма, а не строкой input, по которой обычно сравнивают.
Claude Sonnet 4.5: та же арифметика
Официальный тариф: $2.00 / $10.00 / $0.20.
input: 2.00 × 0.10 = 0.200
output: 10.00 × 0.05 = 0.500
cache read: 0.20 × 0.85 = 0.170
mixed = 0.870 $ / 1M токенов
На 100M токенов — $87.00 по официальному тарифу.
Сводка по профилю 10/5/85
| Модель | Официально in / out / cache read | mixed |
|---|---|---|
| Claude Opus 4.5 | 5.00 / 25.00 / 0.50 | 2.175 |
| Claude Sonnet 4.5 | 2.00 / 10.00 / 0.20 | 0.870 |
Официальные ставки Anthropic сверены 2026-09-09, Sonnet — 2026-09-26, по platform.claude.com/docs/en/about-claude/pricing; ставки шлюза — на странице цен, там же посчитана смешанная цена и экономия по этому же профилю. Подставьте в формулу свои доли из логов: чем выше у вас доля cache read, тем сильнее решает именно эта строка. Обе цены опубликованы рядом на /pricing, машиночитаемый источник правды — GET /api/pricing с версионированным snapshot.
Проверка без пополнения
При регистрации на баланс начисляется $1 welcome-грантом, который тратится на обычные API-запросы. Разделите $1 на смешанную цену нужной модели со страницы цен — получите объём трафика, который грант покрывает. Для миграции и первого взгляда на реальные cost_usd его хватает с запасом. Минимальное пополнение потом — $1, максимум — $10 000, подписок и обязательной ежемесячной платы нет.
Итог
Миграция с прямого Anthropic или OpenAI API на совместимый шлюз — это не рефакторинг, а два изменения в конфиге:
- Anthropic SDK и Claude Code:
base_url=https://go2llm.tech(без/v1), ключ черезANTHROPIC_AUTH_TOKEN. - OpenAI SDK:
base_url=https://go2llm.tech/v1, ключ вapi_key.
Реальная работа не в смене строки, а в чек-листе: взять точный model ID из /v1/models, добавить обработку 402/429/502, выставить квоту на ключ и убедиться, что в запросах нет media на вход и Responses API. Это час работы, не спринт. Полная документация по протоколам и полям — в /docs.
FAQ
Можно ли использовать официальный Anthropic SDK без форков и патчей? Да. Anthropic SDK для Python и JavaScript принимает параметр base_url / baseURL в конструкторе клиента, и этого достаточно. Протокол /v1/messages совпадает по полям запроса, SSE-стримингу и tool calls, поэтому остальной код не меняется. Заголовок anthropic-version: 2023-06-01 сохраняется как есть.
Почему Claude Code возвращает 401, хотя тот же ключ работает в curl? Потому что использована переменная ANTHROPIC_API_KEY вместо ANTHROPIC_AUTH_TOKEN. Если на машине уже есть вход в Claude Code под личным аккаунтом claude.ai или Anthropic Console, ANTHROPIC_API_KEY может уступить сохранённому логину, и запрос уйдёт мимо шлюза с 401. ANTHROPIC_AUTH_TOKEN всегда обращается к указанному ANTHROPIC_BASE_URL с переданным значением.
Чем отличается base URL для Anthropic SDK и для OpenAI SDK? Anthropic SDK и Claude Code ждут базовый адрес без /v1 — https://go2llm.tech, путь SDK дописывает сам. OpenAI SDK и OpenAI-совместимые клиенты ждут его с /v1 — https://go2llm.tech/v1. Перепутанный вариант обычно выглядит как 404 на несуществующем пути.
Почему Codex CLI не подключается к некоторым OpenAI-совместимым шлюзам? Актуальные версии Codex CLI для кастомных провайдеров в ~/.codex/config.toml поддерживают только wire_api = "responses", то есть формат OpenAI Responses API. Значение wire_api = "chat" сам Codex CLI отклоняет с ошибкой «no longer supported». Если у шлюза endpoint /v1/responses не опубликован, Codex получает 404. У go2llm он опубликован и проверен живым прогоном 2026-09-14 — Codex CLI подключается; другие проверенные варианты для терминала — Claude Code и OpenCode.
Как понять, сколько стоил конкретный запрос, не заходя в кабинет? Шлюз возвращает стоимость каждого запроса в поле usage.cost_usd прямо в ответе, включая финальный usage-блок SSE-стрима. Списание работает так: сначала резервируется оценочная стоимость, затем списывается фактическая, остаток резерва освобождается. Баланс при этом не уходит ниже нуля, а при нехватке средств приходит 402 insufficient_balance.
Получить ключ и сделать первый запрос за 3 минуты
Ключ выдаётся сразу, при регистрации на баланс начисляется $1 — хватит, чтобы прогнать миграцию целиком:
curl https://go2llm.tech/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "content-type: application/json" \
-d '{"model":"gpt-5.6-sol","max_tokens":120,"messages":[{"role":"user","content":"Ответь одним словом: работает"}]}'