Saltar al contenido principal

Cambiar la base_url de OpenAI: qué ajuste gana y qué ruta poner

El argumento base_url gana a OPENAI_BASE_URL. La URL debe llevar el prefijo del proveedor (/v1, /openai/v1, /v1beta/openai), nunca la ruta /chat/completions.

LaoZhang AI TeamPublicado10 min de lectura
En esta página
Orden de prioridad de la URL base en los SDK de OpenAI: argumento base_url, variable OPENAI_BASE_URL y api.openai.com/v1 por defecto, con los prefijos válidos /v1, /openai/v1/ y /v1beta/openai/

En los SDK oficiales de OpenAI, la URL base se decide en este orden: el argumento del constructor (base_url en Python, baseURL en Node), después la variable de entorno OPENAI_BASE_URL y, si no hay ninguna, https://api.openai.com/v1. El SDK añade a esa base la ruta de cada método (/chat/completions, /responses), así que la base tiene que terminar justo donde termina el prefijo del proveedor: /v1 en una pasarela típica, /openai/v1/ en Azure OpenAI y /v1beta/openai/ en Gemini.

El comportamiento descrito aquí corresponde a openai-python 3.19.2 y openai-node 7.23.0, las versiones publicadas a 26 de septiembre de 2026, comprobado contra un servidor de eco local que registra la ruta de cada petición. Si usas Cursor, su campo "Override OpenAI Base URL" funciona con otras reglas; están más abajo.

Qué valor gana cuando hay varios

Es fácil tener la base configurada en un sitio mientras el cliente la lee de otro. Esta tabla recoge lo que hace cada SDK cuando conviven varios ajustes:

SituaciónPython (openai 3.19.2)Node (openai 7.23.0)
Argumento del constructor y OPENAI_BASE_URL a la vezGana el argumentoGana el argumento
Solo OPENAI_BASE_URLSe usa la variableSe usa la variable
Ninguno de los doshttps://api.openai.com/v1https://api.openai.com/v1
OPENAI_BASE_URL definida pero vacíaLa base queda vacía y la llamada falla con APIConnectionError: Connection error.Vuelve sin avisar a https://api.openai.com/v1
Solo la variable antigua OPENAI_API_BASESe ignora: el cliente sigue en api.openai.comSe ignora igual
data_residency="eu" / dataResidency: 'eu' con OPENAI_BASE_URL definidaGana la residencia: https://eu.api.openai.com/v1Igual
baseURL: null con la variable definida—No lee la variable y usa el valor por defecto
with_options(base_url=...) / withOptions({ baseURL })Cambia solo esa llamada; el cliente conserva su baseIgual

Tres filas merecen atención. La variable vacía se comporta distinto en cada lenguaje: en Node la petición acaba en OpenAI con una clave que quizá sea de otro proveedor, y OpenAI la rechazará como clave incorrecta. La variable OPENAI_API_BASE viene de la librería anterior a la versión 1 (se usaba junto a openai.api_base) y sigue apareciendo en tutoriales de 2023 y 2024; los SDK actuales no la leen, aunque algún framework puede leerla por su cuenta, así que consulta su documentación antes de dar por hecho cuál usa. Y la residencia de datos no es una URL que escribas a mano: el SDK conoce los hosts us.api.openai.com, eu.api.openai.com y ae.api.openai.com y los elige con data_residency. En Python, combinar data_residency o provider con base_url lanza un error al crear el cliente; en Node, provider no se puede combinar con baseURL ni con dataResidency.

La variable se lee al construir el cliente. Si la defines con os.environ después de crear OpenAI(), ese cliente no la verá.

Cómo une el SDK la base y la ruta

Python normaliza la base añadiéndole una barra final y luego le pega la ruta relativa del método; Node produce el mismo resultado. Estas son las rutas que llegaron al servidor de eco con chat.completions.create en ambos SDK:

Base que pasasRuta que recibe el servidor
http://[::1]:8765/v1/v1/chat/completions
http://[::1]:8765/v1//v1/chat/completions (sin doble barra)
http://[::1]:8765/chat/completions
http://[::1]:8765/v1/chat/completions/v1/chat/completions/chat/completions

Con responses.create y la base .../v1, la petición llega a POST /v1/responses.

De ahí sale la regla práctica: toma la URL completa del endpoint de chat que documenta el proveedor y quítale /chat/completions. Lo que queda es tu base. La barra final da igual; lo que rompe la llamada es que falte un segmento del prefijo o que sobre la ruta del método.

Cuatro bases unidas a /chat/completions: /v1 y /v1/ llegan a /v1/chat/completions, solo el host pierde el prefijo y la base con /chat/completions duplica la ruta

Qué URL base escribir para cada destino

DestinoURL basemodelQué API documenta
OpenAI (global)Nada, o https://api.openai.com/v1ID de modelo de OpenAITodas las del SDK
OpenAI con residencia de datosNo uses base_url: data_residency="us", "eu" o "ae"ID de modelo de OpenAILas del SDK, si tu organización tiene acceso a esa región
Azure OpenAI (API v1)https://TU-RECURSO.openai.azure.com/openai/v1/Nombre de la implementación (deployment)Chat Completions y Responses, con un subconjunto de funciones en la versión GA
Gemini (compatibilidad con OpenAI)https://generativelanguage.googleapis.com/v1beta/openai/ID de modelo de GeminiChat Completions, embeddings, salida estructurada, imagen, vídeo, audio, batch; Responses no figura
Pasarela compatible, por ejemplo laozhang.aihttps://api.laozhang.ai/v1ID que publique la pasarelaChat Completions; según su documentación, Responses solo para los modelos GPT-6
Servidor local o propioLo que preceda a /chat/completions en su documentación, p. ej. http://host:puerto/v1El nombre que sirva el servidorLa que implemente el servidor

En Azure, Microsoft Learn describe la API v1 como un cambio de dos cosas: usar el cliente OpenAI() normal en lugar de AzureOpenAI() y añadir /openai/v1/ al punto de conexión del recurso. Ya no hace falta api-version. También vale el dominio *.services.ai.azure.com/openai/v1/, y las variables OPENAI_BASE_URL y OPENAI_API_KEY funcionan con OpenAI() sin parámetros:

python
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["AZURE_OPENAI_API_KEY"],
    base_url="https://TU-RECURSO.openai.azure.com/openai/v1/",
)

respuesta = client.responses.create(
    model="mi-implementacion",  # nombre de la implementación, no del modelo
    input="Prueba",
)

El cliente antiguo AzureOpenAI(azure_endpoint=..., api_version=...) sigue existiendo en el SDK; no mezcles sus parámetros con los de la API v1. Si en Azure la base ya es correcta y lo que ves son errores 429, el problema es de cuota y se diagnostica aparte: Límite TPM de Azure OpenAI: cómo diagnosticar y resolver los errores 429.

Con Gemini, la página de compatibilidad de Google (actualizada el 2 de septiembre de 2026) indica que el soporte de las librerías de OpenAI sigue en beta y no documenta un endpoint /responses. Eso no demuestra que nunca vaya a funcionar, pero tampoco es algo en lo que apoyarte: en noviembre de 2025 un usuario del foro de la comunidad de OpenAI obtuvo un 404 al llamar a client.responses.create con esa base. Si tu código usa Responses y el destino es Gemini, cambia esas llamadas a chat.completions.create.

Cambiar solo la URL base basta cuando se cumplen dos condiciones: que la ruta encaje con la tabla anterior y que el destino implemente la API concreta que llama tu código.

Proxy y URL base son ajustes distintos

"Proxy" se usa para dos cosas. Si hablas de un servicio intermediario que expone una API compatible (una pasarela), eso se configura con la URL base. Si hablas de un proxy HTTP por el que tiene que salir el tráfico de tu red, eso es un salto de red y se configura en el cliente HTTP, sin tocar la base:

python
from openai import OpenAI, DefaultHttpx2Client

client = OpenAI(
    base_url="https://api.ejemplo.com/v1",  # origen de la API
    http_client=DefaultHttpx2Client(proxy="http://proxy.miempresa.local:3128"),  # salto de red
)

Los nombres DefaultHttpx2Client y httpx2 son los del README actual; versiones anteriores del SDK usaban DefaultHttpxClient, así que revisa cuál trae la tuya. En Node, el proxy va en fetchOptions, por ejemplo con ProxyAgent de undici como dispatcher y el fetch de undici, y la base sigue en baseURL.

Si la petición no llega a ninguna parte (tiempo de espera agotado, DNS, TLS), el problema suele estar en ese salto y no en la base; el diagnóstico por capas está en Error de conexión de OpenAI API: corrige APIConnectionError probando la ruta.

Comprueba adónde van realmente las peticiones

Antes de culpar al proveedor, confirma el host y la ruta que usa tu proceso. De más barato a más completo:

  1. Imprime la base efectiva. print(client.base_url) en Python o console.log(client.baseURL) en Node, justo después de crear el cliente y en el mismo proceso que falla (no en una consola aparte con otras variables).
  2. Activa los logs del SDK. OPENAI_LOG=info o OPENAI_LOG=debug en Python; en Node, la misma variable o la opción logLevel: 'debug' del cliente. El formato de estos mensajes puede cambiar entre versiones.
  3. Apunta a un servidor de eco local. No necesitas clave real ni gastar tokens: el servidor imprime el método, la ruta y el inicio de la cabecera Authorization, y devuelve una respuesta mínima de chat.
  4. Mira los logs del proveedor. Si tu código dice que llama a un proveedor y en su panel no aparece ni la petición ni un 401, la petición no llegó.

El servidor de eco solo usa la biblioteca estándar de Python:

python
# eco.py
import json
import socket
from http.server import BaseHTTPRequestHandler, HTTPServer

class Eco(BaseHTTPRequestHandler):
    def do_POST(self):
        self.rfile.read(int(self.headers.get("Content-Length", 0)))
        auth = self.headers.get("Authorization", "")[:12]
        print(f"{self.command} {self.path}  host={self.headers.get('Host')}  auth={auth}...")
        body = json.dumps({
            "id": "eco", "object": "chat.completion", "created": 0, "model": "eco",
            "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

class ServidorIPv6(HTTPServer):
    address_family = socket.AF_INET6

ServidorIPv6(("::1", 8765), Eco).serve_forever()

Arráncalo con python eco.py y, en otra terminal, lanza tu código con la misma configuración que en producción cambiando solo la base:

python
# prueba.py
from openai import OpenAI

client = OpenAI(api_key="sk-prueba", max_retries=0)
print("base efectiva:", client.base_url)
client.chat.completions.create(
    model="eco", messages=[{"role": "user", "content": "hola"}]
)
bash
OPENAI_BASE_URL=http://[::1]:8765/v1 python prueba.py

El servidor debería imprimir POST /v1/chat/completions. Si imprime /v1/chat/completions/chat/completions, estás pasando la ruta del método dentro de la base; si el servidor no imprime nada y el script falla con un 401, la petición salió hacia otro host: tu proceso no está leyendo la variable. La respuesta de eco sirve para chat.completions; con responses.create verás la ruta en el servidor, pero el SDK no podrá interpretar la respuesta, y eso es lo esperado.

Del síntoma a la capa que hay que corregir

Lo que vesQué suele significarQué cambiar
404 y el log muestra /chat/completions/chat/completionsLa base incluye la ruta del métodoQuita /chat/completions de la base
404 y la ruta es /chat/completions sin prefijoFalta /v1, /openai/v1 o /v1beta/openaiAñade el prefijo que documenta el proveedor
404 solo en responses.createEl destino no implementa Responses (caso de Gemini)Usa chat.completions.create o un destino que documente /responses
401 de OpenAI ("Incorrect API key provided") con una clave de otro proveedorLa petición fue a api.openai.com: base no aplicada, OPENAI_API_BASE en lugar de OPENAI_BASE_URL, variable vacía en Node o data_residency activaImprime la base efectiva y corrige de dónde se lee
401 del proveedor correctoLa clave no es de ese destino o no tiene permisoUsa la clave que emite ese proveedor
Modelo no encontradoEl ID pertenece a otro proveedor, o en Azure pasas el nombre del modelo en lugar del de la implementaciónUsa el ID del destino (en Azure, la implementación)
APIConnectionError: Connection error. en PythonBase vacía (OPENAI_BASE_URL="") o host inalcanzableBorra la variable vacía; si la base es correcta, revisa red y proxy

La clave pertenece a quien la emite: una clave de Gemini no sirve en Azure y una de Azure no sirve en OpenAI. Si dudas de qué credenciales necesita cada llamada, OpenAI API Key y Organization ID: lo que de verdad necesitas en 2026 lo separa. Si el cliente que cambias es Codex y no un SDK, su configuración tiene nombres propios (openai_base_url, proveedores personalizados): Custom Provider en Codex: API Key, Base URL y config.toml.

Override OpenAI Base URL en Cursor

En Cursor, el campo se llama "Override OpenAI Base URL" y está en Cursor Settings > Models, junto a la clave de OpenAI. La página de ayuda de Cursor no lo documenta; lo que sigue viene de respuestas del personal de Cursor en su foro entre agosto y septiembre de 2026, y algunas describen limitaciones conocidas sin fecha de solución.

A qué modelos afecta. La clave de OpenAI y la URL de override se aplican a todos los modelos que no son de Claude ni de Gemini, incluidos Composer, Grok y los modelos de OpenAI del selector integrado (respuestas del 21 de agosto y del 23 de septiembre de 2026). Composer y Grok corren en la infraestructura de Cursor y rechazan claves propias con "This model does not support custom API keys". No se puede tener a la vez los modelos propios en tu endpoint y los de Cursor en Cursor: hay que desactivar el override para usar los de Cursor y volver a activarlo para los tuyos.

Nombres reservados. Si el ID de tu modelo personalizado coincide con uno que Cursor sirve por su cuenta (en agosto y septiembre de 2026 pasaba con kimi-k3, kimi-k2.6 o kimi-latest, entre otros), Cursor lo enruta a su propio backend y la petición nunca llega a tu URL. Usa un ID real del proveedor que no choque con esos nombres.

Modelos de Cursor frente al override: Claude y Gemini no lo usan, Composer y Grok rechazan claves propias, los ID reservados van al backend de Cursor y los modelos de OpenAI o tu ID propio llegan a tu URL HTTPS pública

Pasos que se suelen saltar.

  1. Pegar la clave y activar "Use OpenAI API key" son dos pasos distintos; el override solo actúa con ese interruptor encendido.
  2. Al activar el override, el campo se rellena con https://api.openai.com/v1. Si lo desactivas y lo vuelves a activar, vuelve a ese valor. Escribe la URL de tu proveedor, pulsa Enter o haz clic fuera para guardarla, cierra y abre Settings para confirmar que sigue ahí.
  3. Abre un chat nuevo después de cambiar la configuración.

Si te olvidas del paso 2, tu clave de otro proveedor viaja a api.openai.com y OpenAI responde "Incorrect API key provided"; en el panel de tu proveedor no verás ningún 401 porque la petición nunca llegó. En las versiones 3.15.x hubo un fallo por el que los campos no aceptaban el clic; el apaño era activar el interruptor y entrar en el campo con Tab.

Nada de direcciones locales. Todas las peticiones con clave propia pasan por los servidores de Cursor, que construyen el prompt, así que el override no llega a direcciones locales ni de la red interna: necesita un endpoint HTTPS público. Para un modelo local, el personal de Cursor propone un túnel (ngrok, Cloudflare Tunnel). Un túnel expone tu servidor a internet: ponle autenticación antes de publicarlo.

Qué no cambia. Las claves propias solo se usan en los modelos de chat; el autocompletado con Tab sigue usando los modelos de Cursor. La política de retención cero de datos de Cursor no se aplica cuando usas tu propia clave, y, según su página de ayuda a 26 de septiembre de 2026, en los planes Team y Enterprise cada petición con clave propia se sigue cobrando a la tarifa de tokens de Cursor, 0,25 $ por millón de tokens.

Preguntas frecuentes

¿Sigue funcionando OPENAI_API_BASE?

No con los SDK oficiales actuales: openai-python y openai-node solo leen OPENAI_BASE_URL, y con OPENAI_API_BASE sola el cliente sigue apuntando a api.openai.com. Algunos frameworks pueden seguir leyendo el nombre antiguo por su cuenta; comprueba en su documentación qué variable usan.

¿Puedo cambiar la base solo para una llamada?

Sí. client.with_options(base_url="...") en Python y client.withOptions({ baseURL: "..." }) en Node desvían únicamente la llamada encadenada; el cliente original conserva su base.

¿Cualquier servicio compatible con OpenAI acepta Responses API?

No hay garantía. Azure OpenAI documenta responses.create en su API v1; la compatibilidad de Gemini no incluye /responses; cada pasarela publica su propia lista, y a veces solo para algunos modelos. Si estás pasando código de Assistants a Responses y quieres que siga funcionando fuera de OpenAI, confirma el soporte del destino antes de migrar: Cómo migrar de Assistants API a Responses API sin cortar producción.