OpenAI base_url в SDK и Cursor: приоритет, путь /v1 и проверка
Аргумент base_url в коде сильнее OPENAI_BASE_URL. В base пишут весь префикс до имени метода: /v1, /openai/v1 или /v1beta/openai, но не полный endpoint.
Содержание

Если код или инструмент работает в формате OpenAI API, адрес назначения меняется одной настройкой — base URL. Но запрос приходит туда, куда вы рассчитывали, только при трёх условиях: SDK взял именно ваше значение; в строке есть весь префикс пути, который ждёт сервис; сервис реализует тот API, который вы вызываете.
Для официальных SDK — openai-python 3.19.2 и openai для Node 7.23.0, последних версий по состоянию на 26 сентября 2026 года — ответ такой:
- аргумент конструктора (
base_urlв Python,baseURLв Node) сильнее переменной окруженияOPENAI_BASE_URL, а она сильнее значения по умолчаниюhttps://api.openai.com/v1; - SDK дописывает к base путь метода (
chat/completions,responses), поэтому в base пишут всё до имени метода —…/v1,…/openai/v1/,…/v1beta/openai/, — но не полный endpoint; - HTTP-прокси задаёт сетевой маршрут, а не адрес API, и настраивается отдельно от base URL;
- после настройки фактический адрес нужно увидеть: вывести
client.base_url, прогнать echo-проверку или найти запрос в логах провайдера.
Приоритет настроек и склейка путей ниже проверены запуском обоих SDK против echo-сервера на том же компьютере с фиктивным ключом. Такая проверка показывает, какой URL строит SDK, но ничего не говорит о том, как ответит конкретный провайдер.
Какое значение возьмёт SDK
| Что задано | openai-python 3.19.2 | openai (Node) 7.23.0 |
|---|---|---|
| Ничего | https://api.openai.com/v1 | https://api.openai.com/v1 |
Только OPENAI_BASE_URL | значение переменной | значение переменной |
OPENAI_BASE_URL и аргумент в коде | аргумент | аргумент |
OPENAI_BASE_URL="" (задана, но пустая) | base остаётся пустым, запрос падает с APIConnectionError: Connection error. | пустое значение считается незаданным, запрос уходит на https://api.openai.com/v1 |
Только старая OPENAI_API_BASE | игнорируется, остаётся https://api.openai.com/v1 | игнорируется, остаётся https://api.openai.com/v1 |
OPENAI_BASE_URL и data_residency="eu" / dataResidency: 'eu' | https://eu.api.openai.com/v1 | https://eu.api.openai.com/v1 |
OPENAI_BASE_URL и baseURL: null | — | переменная не читается, https://api.openai.com/v1 |
Три строки этой таблицы требуют пояснений.
Пустая переменная. В Python она превращается в ошибку соединения, которую легко принять за сетевую проблему. В Node всё тише: клиент молча возвращается на OpenAI и отправляет туда ключ другого провайдера, так что ждать стоит отказа авторизации от OpenAI. Проверьте, что реально лежит в окружении процесса, например printenv OPENAI_BASE_URL, а в контейнерах и CI — в описании переменных самой среды.
Старое имя OPENAI_API_BASE. Так переменная называлась в openai-python до версии 1 (вместе с openai.api_base), и многие старые руководства до сих пор её показывают. Текущие SDK читают только OPENAI_BASE_URL. Некоторые сторонние фреймворки читают собственные переменные, поэтому для них имя сверяйте с документацией фреймворка, а не SDK.
Регион OpenAI. Параметр data_residency (us, eu, ae) выбирает https://us.api.openai.com/v1, https://eu.api.openai.com/v1 или https://ae.api.openai.com/v1 и перекрывает OPENAI_BASE_URL. Передать его вместе с явным base_url Python не даст: конструктор выбросит ValueError: The data_residency and base_url arguments are mutually exclusive. Параметр provider= тоже сам задаёт адрес и с base_url не сочетается ни в одном из двух SDK. SDK подставляет адрес из своего списка, а условия доступа к региональным endpoint'ам задаёт OpenAI, не SDK.
Минимальная явная настройка выглядит так. Ключ передаётся явно: без этого SDK возьмёт OPENAI_API_KEY, и ключ может оказаться от одного сервиса, а адрес — от другого.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.example.com/v1", # сильнее OPENAI_BASE_URL
api_key=os.environ["PROVIDER_API_KEY"],
)
print(client.base_url) # адрес, который SDK реально будет использоватьimport OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://api.example.com/v1',
apiKey: process.env.PROVIDER_API_KEY,
});
console.log(client.baseURL);Если другой адрес нужен только одному вызову, используйте client.with_options(base_url=...) в Python или client.withOptions({ baseURL }) в Node. Этот вызов уйдёт на новый адрес, а сам клиент сохранит прежний base.
Что писать в base URL
SDK не угадывает структуру чужого API: он берёт base и приписывает к нему относительный путь метода. Python дополнительно добавляет в конец base косую черту, поэтому …/v1 и …/v1/ дают одинаковый результат. Вот какие пути получил echo-сервер от обоих SDK при вызове chat.completions.create:
| Значение base URL | Путь запроса на сервере |
|---|---|
https://host/v1 | /v1/chat/completions |
https://host/v1/ | /v1/chat/completions, без двойной косой черты |
https://host | /chat/completions |
https://host/v1/chat/completions | /v1/chat/completions/chat/completions |
Вызов responses.create точно так же уходит на {base}/responses. Отсюда правило: base URL — это всё, что стоит перед именем метода. Если сервис публикует https://host/openai/v1/chat/completions, в base пишут https://host/openai/v1. Полный адрес метода в base — самая частая причина 404 с «удвоенным» путём.

Для распространённых целей это выглядит так:
| Куда отправлять | base URL | Ключ и model | Что учесть |
|---|---|---|---|
| OpenAI, глобальный endpoint | https://api.openai.com/v1, задавать не нужно | ключ OpenAI, ID моделей OpenAI | — |
| OpenAI, региональный endpoint | через data_residency, не через base_url | ключ OpenAI | см. раздел выше |
| Azure OpenAI, v1 API | https://ИМЯ-РЕСУРСА.openai.azure.com/openai/v1/ или https://ИМЯ-РЕСУРСА.services.ai.azure.com/openai/v1/ | ключ Azure или токен Entra ID; model — имя deployment | api-version больше не нужен; в v1 GA поддержана только часть возможностей |
| Gemini API | https://generativelanguage.googleapis.com/v1beta/openai/ | ключ Gemini API, ID моделей Gemini | поддержка библиотек OpenAI в бете; Chat Completions описан, Responses нет |
| Шлюз laozhang.ai | https://api.laozhang.ai/v1 | ключ шлюза, ID из его каталога | Responses заявлен для GPT-6 Astra, Sol и Luna, а не для всех моделей |
| Свой или локальный сервер | префикс, под которым сервер отдаёт chat/completions | как настроено на сервере | путь сверьте echo-проверкой или логом сервера |
Строка Azure взята из описания v1 API в документации Microsoft: там показан обычный клиент OpenAI() с таким base_url, а переменные OPENAI_BASE_URL и OPENAI_API_KEY работают с ним без параметров. Старый клиент AzureOpenAI(azure_endpoint=..., api_version=...) в SDK остался, но это другая схема адреса. Ошибка 429 после переключения на Azure значит, что адрес уже верный, а упираетесь вы в лимиты deployment, — см. Лимиты TPM в Azure OpenAI: как найти причину ошибки 429.
Строка Gemini соответствует странице совместимости с OpenAI, обновлённой 2 сентября 2026 года. Для laozhang.ai адрес и ограничение по Responses указаны в документации шлюза и объявлении о запуске GPT-6.
Прокси и base URL — разные настройки
По-русски «прокси» называют и сервис-посредник, и сетевой прокси, а для SDK это разные вещи. Если посредник принимает запросы API по собственному адресу и сам пересылает их провайдеру, это шлюз, и его адрес идёт в base URL. Если же трафик к api.openai.com просто должен пройти через HTTP-прокси, base URL не меняется, а прокси задаётся в HTTP-клиенте.
В openai-python 3.19.2:
from openai import OpenAI, DefaultHttpx2Client
client = OpenAI(
http_client=DefaultHttpx2Client(proxy="http://proxy.example.com:3128"),
)В более старых версиях Python SDK этот класс назывался DefaultHttpxClient, поэтому пример из свежего README может не импортироваться в старой установке. В Node прокси передают через fetchOptions:
import OpenAI from 'openai';
import { fetch, ProxyAgent } from 'undici';
const client = new OpenAI({
fetch,
fetchOptions: { dispatcher: new ProxyAgent('http://proxy.example.com:3128') },
});Адрес HTTP-прокси, записанный в base URL, превращает прокси в получателя запросов API, которых он не понимает. Если в итоге ответа нет вовсе, разбирайте сетевую часть: Ошибка подключения OpenAI API: сначала проверьте маршрут APIConnectionError.
Реализует ли сервис вызываемый API
«OpenAI-совместимый» обычно означает прежде всего совместимость с Chat Completions (POST {base}/chat/completions). Responses API (POST {base}/responses) — отдельный набор endpoint'ов, и правильный base URL не поможет, если сервис его не реализует.
Типичный пример — ветка на форуме сообщества OpenAI от 6 ноября 2025 года: client.responses.create с base URL совместимости Gemini вернул 404. Ответ дал участник сообщества, а не сотрудник: другие компании, как правило, поддерживают совместимость только с Chat Completions. Документация Gemini по состоянию на 2 сентября 2026 года описывает chat completions, embeddings, изображения, аудио, пакетную обработку и список моделей, но не Responses. Отсутствие в документации не доказывает, что endpoint никогда не заработает, но строить на нём рабочий код не стоит.
Решение зависит от того, какой API вызывает ваш код:
- Chat Completions. Подойдёт любой сервис, который документирует
chat/completions. Потоковую выдачу, function calling и structured outputs проверяйте отдельно: у каждого провайдера свой набор. - Responses (Agents SDK или код после миграции с OpenAI Assistants API на Responses API). Нужен сервис, который документирует
/responsesдля вашей модели: OpenAI, Azure v1 API (в документации Microsoft есть пример…/openai/v1/responses) или шлюз, заявивший Responses для конкретных моделей. Иначе переводите вызов на Chat Completions или ставьте прослойку-адаптер вроде LiteLLM.
Как доказать, куда уходит запрос
Фраза «я же задал переменную» ничего не доказывает, пока вы не увидели адрес. Проверяйте по нарастающей, пока не найдёте расхождение.
- Выведите итоговый base.
print(client.base_url)илиconsole.log(client.baseURL)в том же процессе и с тем же окружением, где работает приложение. Здесь сразу видны проигранный приоритет, пустая переменная и уход на значение по умолчанию. - Прогоните echo-проверку. Скрипт ниже поднимает на вашем компьютере сервер на loopback-адресе IPv6
::1, направляет на него SDK и печатает путь, который пришёл на сервер. Наружу ничего не отправляется, ключ фиктивный. - Включите журнал SDK. В Python —
OPENAI_LOG=debug(илиinfo); переменная настраивает логгерopenai, а логгеры HTTP-транспорта настраиваются отдельно черезlogging. В Node — та же переменная или опция клиентаlogLevel: 'debug': на этом уровне пишутся метаданные и заголовки запросов и ответов, заголовки авторизации скрыты. - Посмотрите на стороне провайдера. Если в его логах или аналитике запроса нет, он туда не дошёл, и ошибку возвращал кто-то раньше — SDK, прокси или промежуточный сервис.
# echo_check.py — запросы не покидают компьютер
import json, socket, sys, threading
from http.server import BaseHTTPRequestHandler, HTTPServer
from openai import OpenAI
class LoopbackV6(HTTPServer):
address_family = socket.AF_INET6
class Echo(BaseHTTPRequestHandler):
def do_POST(self):
self.rfile.read(int(self.headers.get("Content-Length", 0)))
print("сервер получил:", self.command, self.path)
body = json.dumps({
"id": "echo", "object": "chat.completion", "created": 0, "model": "echo",
"choices": [{"index": 0, "finish_reason": "stop",
"message": {"role": "assistant", "content": "ok"}}],
}).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *args):
pass
server = LoopbackV6(("::1", 0), Echo) # ::1 — loopback IPv6, порт выбирает ОС
threading.Thread(target=server.serve_forever, daemon=True).start()
prefix = sys.argv[1] if len(sys.argv) > 1 else "/v1" # проверяемая часть пути
client = OpenAI(base_url=f"http://[::1]:{server.server_address[1]}{prefix}",
api_key="sk-fake", max_retries=0)
print("client.base_url:", client.base_url)
client.chat.completions.create(model="echo", messages=[{"role": "user", "content": "ping"}])
server.shutdown()Передайте ту часть пути, которую собираетесь записать после хоста:
python echo_check.py /openai/v1/
# client.base_url: http://[::1]:49663/openai/v1/
# сервер получил: POST /openai/v1/chat/completions
python echo_check.py /v1/chat/completions
# client.base_url: http://[::1]:49669/v1/chat/completions/
# сервер получил: POST /v1/chat/completions/chat/completionsПорт в выводе каждый раз другой. Если в вашей среде отключён IPv6 (так бывает в некоторых контейнерах), замените ::1 и [::1] на IPv4-адрес loopback, а AF_INET6 — на AF_INET.
Что чинить по симптому
| Что видите | Где причина | Что сделать |
|---|---|---|
404 на любом методе, в логе путь с /chat/completions/chat/completions или без нужного префикса | строка base URL | оставить в base всё до имени метода, сверить echo-проверкой |
404 только на responses.create, Chat Completions работает | сервис не реализует Responses | перейти на Chat Completions или на сервис с документированным /responses |
401 или Incorrect API key provided с ключом не от OpenAI | запрос ушёл на api.openai.com: пустая переменная в Node, старое OPENAI_API_BASE, сброс настройки в инструменте | вывести client.base_url, исправить источник адреса |
| 401 от нужного хоста | ключ не от этого сервиса или SDK взял OPENAI_API_KEY | передать ключ явно; какие идентификаторы вообще нужны OpenAI — в статье OpenAI API Key и Organization ID: что реально нужно в 2026 году |
model not found | ID модели из чужого каталога | для Azure — имя deployment, для Gemini — ID Gemini, для шлюза — ID из его каталога |
APIConnectionError, ответа нет | сеть, прокси или пустой base в Python | проверить переменную, затем маршрут |
Cursor: Override OpenAI Base URL
В Cursor ту же роль играет поле Override OpenAI Base URL в Cursor Settings > Models. Справка Cursor по собственным ключам его не описывает. Она сообщает другое: свои ключи действуют только для моделей чата, Tab по-прежнему работает на моделях Cursor, а все запросы идут через серверы Cursor, где собирается итоговый промпт. Всё остальное ниже — ответы сотрудников Cursor на форуме за 2026 год, даты указаны у каждого факта. Это не документация, и поведение может меняться между версиями.
Порядок настройки, при котором запрос действительно доходит до вашего endpoint:
- Вставьте ключ провайдера в поле OpenAI API Key и включите переключатель Use OpenAI API key. Это два отдельных шага. Override работает, только пока ключ активен; без него запросы приходят в Cursor без ключа и отклоняются с
ERROR_BAD_MODEL_NAME(ответ от 25 августа 2026 года, Cursor 3.17.19). - Включите Override OpenAI Base URL. Поле само заполнится адресом
https://api.openai.com/v1— замените его на base URL провайдера, напримерhttps://api.fireworks.ai/inference/v1, нажмите Enter или щёлкните вне поля, закройте и снова откройте настройки и убедитесь, что адрес сохранился. После выключения и повторного включения переключатель снова подставит адрес OpenAI, и ключ провайдера уйдёт в OpenAI с ответомIncorrect API key provided(ответ от 10 сентября 2026 года). - Добавьте свою модель под настоящим ID провайдера, который не совпадает со встроенными именами Cursor. В августе 2026 года, например,
kimi-k2.6,kimi-k3иkimi-latestсчитались зарезервированными: Cursor обрабатывал их как свои модели и не отправлял на Override. ID моделей, которые Cursor хостит сам (в сентябрьском ответе —kimi-k3иglm-5p2на Fireworks), он маршрутизирует независимо от base URL, поэтому как контрольная проверка они не годятся. - Начните новый чат.
Перед включением стоит учесть охват настройки. По ответу от 23 сентября 2026 года OpenAI API key и Override OpenAI Base URL действуют на все модели, кроме Claude и Gemini, — включая Composer и Grok. Эти модели работают на инфраструктуре Cursor и с вашим ключом отклоняются: This model does not support custom API keys. Встроенные модели семейства OpenAI из списка выбора при включённом Override тоже уходят на ваш endpoint. Одновременно держать свои модели на Override, а модели Cursor на Cursor пока нельзя. Маршрутизацию по отдельным моделям в Cursor отслеживают, но сроков не называют. Обходной путь — выключать Override (или OpenAI key) на время работы с моделями Cursor.

Локальные адреса и адреса внутренней сети Cursor недоступны: запросы идут через его серверы, поэтому нужен публичный HTTPS-адрес. В ответах с февраля по май 2026 года сотрудники Cursor предлагают туннель (ngrok, Cloudflare Tunnel). Туннель открывает сервер модели всему интернету, так что включите на нём проверку ключа — это вопрос безопасности, а не условие Cursor.
Если что-то не работает, сохраните Request ID из Cursor и посмотрите аналитику провайдера. В разобранном на форуме случае с Fireworks в аналитике не было ни одной ошибки 401, и это прямо показало, что запросы до Fireworks не доходили: они уходили на адрес OpenAI по умолчанию.
У Codex своя схема настройки — через openai_base_url или отдельный provider в config.toml. Её разбор — в статье Custom provider в Codex: API-ключ, Base URL и config.toml.





