Cómo funciona el gateway
Un solo host sirve las API de Anthropic, OpenAI y Gemini en su formato nativo. Endpoints, URL base, ids de modelo, autenticación y qué cambia Half Off Dev.
La idea de Half Off Dev es que sigas usando el código que ya tienes. Claude, GPT y Gemini se sirven cada uno en el formato de su propio fabricante, desde un único host y a mitad del precio de lista. Tu SDK envía exactamente el mismo JSON que enviaría al fabricante; solo cambian la URL base y la clave.
No hay ninguna capa de traducción en medio. Una petición con formato de Anthropic va a un canal que habla Anthropic, una con formato de OpenAI a uno que habla OpenAI, y así sucesivamente. Salvo por dos ajustes muy concretos, el cuerpo llega tal como lo escribiste, de modo que opciones como thinking, cache_control, tools o generationConfig funcionan tal y como las describe la referencia de cada fabricante.
Endpoints de un vistazo#
Pásale a tu SDK la URL base de esta tabla y deja que él construya la ruta.
| Formato | Rutas | URL base para el SDK | Guía |
|---|---|---|---|
| Anthropic Messages | POST /v1/messages, POST /v1/messages/count_tokens | https://halfoff.dev | Anthropic Messages |
| OpenAI Chat Completions | POST /v1/chat/completions | https://halfoff.dev/v1 | OpenAI |
| OpenAI Responses (solo GPT) | POST /v1/responses | https://halfoff.dev/v1 | Responses API |
| Gemini | POST /v1beta/models/{model}:generateContent, :streamGenerateContent, :countTokens | https://halfoff.dev | Gemini |
| Catálogo de modelos | GET /v1/models, GET /v1beta/models | Precios |
Lo que no aparece en esta lista no está soportado; consulta lo que no puedes llamar.
Cómo enviar tu clave#
Cada endpoint busca la clave en cuatro sitios, así que funciona con lo que cada SDK haga por defecto:
| Ubicación | Quién la usa normalmente |
|---|---|
Authorization: Bearer ho-... | SDK de OpenAI, Claude Code con ANTHROPIC_AUTH_TOKEN, clientes HTTP genéricos |
x-api-key: ho-... | SDK de Anthropic |
x-goog-api-key: ho-... | SDK Google Gen AI, Gemini CLI |
?key=ho-... | Ejemplos REST de Gemini |
Los dos endpoints del catálogo son públicos y no necesitan clave. Cómo crear, limitar y rotar claves se explica en Autenticación.
Formato nativo de cada familia#
Cada familia se diseñó alrededor de una API concreta. Si la llamas ahí, tienes todas las funciones que publica el fabricante.
| Familia | Formato nativo | También se acepta hoy |
|---|---|---|
Claude (claude-*) | Anthropic Messages | OpenAI Chat Completions, Gemini |
GPT (gpt-*) | OpenAI Chat Completions, OpenAI Responses | Anthropic Messages, Gemini |
Gemini (gemini-*) | Gemini | OpenAI Chat Completions, Anthropic Messages |
Si usas un formato que no es el nativo, el canal convierte la petición para el modelo y por el camino pueden perderse o rechazarse opciones propias del fabricante. cache_control y thinking de Anthropic, reasoning_effort de OpenAI y thinkingConfig de Gemini solo están garantizados en el formato de origen del modelo.
Los formatos que acepta un modelo dependen de los canales que lo sirven, así que la lista puede cambiar. El catálogo de modelos muestra los formatos actuales de cada uno. Si ningún canal acepta tu formato, recibes 404 con un mensaje como Model 'gpt-6.1-sol' is not served in gemini format in any group.
Ids de modelo#
Los ids canónicos son los de GET /v1/models, por ejemplo claude-opus-5.5, gpt-6.1-sol o gemini-3-flash. Las herramientas que escriben los ids a su manera también llegan al modelo correcto, porque el gateway normaliza lo que recibe:
| Envías | Se interpreta como |
|---|---|
Claude-Opus-5-5 | claude-opus-5.5 |
anthropic/claude-opus-5.5 | claude-opus-5.5 |
claude-haiku-4-5-20251001 | claude-haiku-4.5 |
gemini-3.1-pro-preview | gemini-3.1-pro |
models/gemini-3-flash | gemini-3-flash |
La normalización pasa el id a minúsculas, quita un prefijo vendor/ o models/, trata . y - como el mismo carácter y elimina una fecha final (-20251001), -preview o -latest. La facturación, los registros y las listas de modelos permitidos de cada clave usan siempre el id canónico.
Un id que no corresponde a ningún modelo devuelve 404: Model '<id>' is not available. See GET /v1/models.
El recorrido de una petición#
- Clave. Una clave inactiva o caducada recibe
401. - Modelo. El id se normaliza como se explica arriba y se compara con la lista de modelos permitidos de la clave; un modelo fuera de ella recibe
403. - Saldo. Un saldo igual o inferior a cero, o una clave que agotó su límite de gasto, recibe
402. - Límites de la clave. Se aplican los topes de peticiones por minuto y de concurrencia de la clave (
429). Detalles en Límites y conmutación por error. - Elección de canal. Una conversación ya en marcha vuelve al canal que guarda su caché de prompt; una nueva va al canal más sano de su grupo de canales. Consulta Caché de prompts.
- Llamada al modelo. Si un canal falla antes de producir salida, se sustituye por otro, con un máximo de tres intentos. Los streams se retienen hasta el primer token para que el cambio no se note. Consulta Streaming.
- Medición. Los recuentos de tokens de la respuesta fijan el cargo, a mitad del precio de lista, que se descuenta de tu saldo en segundos. Consulta Precios.
Qué cambia el gateway#
Solo se hacen dos cambios en el cuerpo:
model. El gateway escribe el id tal como lo espera el canal que atiende la petición. Por eso el campomodelde la respuesta puede venir con la grafía del fabricante, por ejemploclaude-opus-5-5.- Uso en los streams de OpenAI. Toda petición de Chat Completions en streaming recibe
stream_options.include_usage: truepara poder facturarla. Tu stream termina, por tanto, con un fragmento extra cuyo arraychoicesestá vacío y que traeusage. Asegúrate de que tu bucle tolere unchoicesvacío.
Las cabeceras que envías se descartan, salvo tres que se reenvían: anthropic-version, anthropic-beta y openai-beta. Si falta anthropic-version, se completa con 2023-06-01. En las rutas de Gemini, los parámetros de consulta como alt=sse pasan; key se elimina.
El cuerpo de la respuesta se devuelve byte a byte. Se conservan content-type y cache-control, y el gateway añade sus propias cabeceras; los ids de petición y las cabeceras de límite de cada fabricante no se reenvían.
Si el modelo rechaza la petición por no ser válida (400, 404, 413, 422), recibes ese estado y el motivo en el formato de error de la API que llamaste. Cualquier otro fallo se reintenta primero en otro canal. Más en Límites y conmutación por error y Errores.
Cabeceras en cada respuesta#
| Cabecera | Contenido |
|---|---|
x-halfoff-request-id | Id de esta llamada, como req_6f1c.... Te lleva a la entrada en Registros; inclúyelo cuando escribas a soporte. |
x-halfoff-group | El grupo de canales que respondió, por ejemplo gpt-value. |
x-halfoff-channel | La etiqueta de canal de ese grupo, codificada para URL, como Pooled%20capacity. |
x-halfoff-queued-ms | Solo si la llamada esperó capacidad: la espera en milisegundos. |
Las tres primeras figuran en access-control-expose-headers, así que el JavaScript del lado del cliente puede leerlas.
Lo que no puedes llamar#
Los endpoints de la tabla de arriba son todo lo que hay para modelos. En concreto, no se sirven:
- OpenAI: respuestas almacenadas (
/v1/responses/{id}), Completions heredado, embeddings, audio, archivos y lotes. La Responses API en sí solo funciona con modelos GPT. - Anthropic: Message Batches, Files y la consulta de un modelo concreto por id.
- Gemini:
cachedContents(caché de contexto explícita), subida de archivos,embedContenty la Live API.
Para configurar un cliente concreto, consulta Integraciones.