Lotes
/v1/files y /v1/batches siguen el formato de OpenAI. Un lote es un archivo de peticiones de chat completions, una por línea, que Zabio responde en menos de 24 horas. Cada línea se clasifica y se enruta como una llamada normal, pero al precio por lotes de los proveedores, la mitad del precio de lista más o menos: la Batch API de OpenAI, los Message Batches de Anthropic, el Batch Mode de Google, los trabajos por lotes de Mistral y la API de lotes de Together para Llama 3.3 70B. Los dos ahorros se suman: la elección del enrutado y el precio por lotes del modelo que el enrutado eligió. El código escrito para el SDK de lotes de OpenAI funciona sin cambios con el base_url de Zabio y una key de Zabio, auto incluido.
Los lotes son para trabajo que nadie está esperando: evaluaciones, rellenos de datos, clasificación, procesos de contenido. Cada línea recibe la chat completion completa que devolvería una llamada síncrona, objeto zabio incluido, así que lo que costó y ahorró cada línea está en el resultado. La key necesita el scope chat.
Cómo funciona
Section titled “Cómo funciona”- Sube un archivo JSONL con
purpose: batch. Cada línea es{"custom_id": "...", "method": "POST", "url": "/v1/chat/completions", "body": {...}}ybodyes una petición normal de chat completions:auto, una línea (auto/best,auto/balanced,auto/cheap) o un modelo con nombre, mensajes, herramientas,response_format,max_tokens. Las líneas se comprueban al subir el archivo; un archivo defectuoso se rechaza con el número de línea. - Crea el lote con el id del archivo,
endpoint: "/v1/chat/completions"ycompletion_window: "24h". Zabio comprueba cada línea, reserva el coste estimado en tu saldo y empieza a enrutar. - Consulta el lote hasta que su estado sea
completed,expired,cancelledofailed. - Descarga el archivo de salida y, si lo hay, el archivo de errores. Los resultados vienen en el orden de tu entrada y llevan tu
custom_id.
curl
cat > requests.jsonl <<'EOF2'{"custom_id": "r1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "auto", "messages": [{"role": "user", "content": "Resume: el informe trimestral muestra ingresos un 12 por ciento arriba."}], "max_tokens": 200}}{"custom_id": "r2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "claude-haiku-4-5", "messages": [{"role": "user", "content": "Traduce al inglés: buenos días."}], "max_tokens": 50}}EOF2
curl https://api.zabio.ai/v1/files \ -H "Authorization: Bearer inserta aquí tu key de zabio.ai" \ -F purpose=batch \ -F file=@requests.jsonlPython
from openai import OpenAIclient = OpenAI(base_url="https://api.zabio.ai/v1", api_key="inserta aquí tu key de zabio.ai")
f = client.files.create(file=open("requests.jsonl", "rb"), purpose="batch")print(f.id, f.bytes) # file_..., el tamaño en claroLa respuesta es un objeto de archivo: id (file_...), bytes, created_at, expires_at, filename, purpose, status (processed). Un expires_after[anchor]=created_at y expires_after[seconds] (de 3600 a 2592000) opcionales acortan cuánto tiempo se conserva el archivo.
curl
curl https://api.zabio.ai/v1/batches \ -H "Authorization: Bearer inserta aquí tu key de zabio.ai" \ -H "Content-Type: application/json" \ -d '{"input_file_id": "file_...", "endpoint": "/v1/chat/completions", "completion_window": "24h", "metadata": {"job": "eval-nocturna"}}'Python
b = client.batches.create(input_file_id=f.id, endpoint="/v1/chat/completions", completion_window="24h", metadata={"job": "eval-nocturna"})print(b.id, b.status) # batch_..., validatingmetadata es opcional: hasta 16 pares, claves de 64 caracteres como máximo, valores de 512 como máximo. La respuesta es el objeto de lote con estado validating.
Consultar
Section titled “Consultar”curl
curl https://api.zabio.ai/v1/batches/batch_... \ -H "Authorization: Bearer inserta aquí tu key de zabio.ai"Python
import timewhile True: b = client.batches.retrieve(b.id) if b.status in ("completed", "expired", "cancelled", "failed"): break time.sleep(60)print(b.status, b.request_counts, b.model_extra["zabio"]["cost"])| Estado | Significado |
|---|---|
validating |
se están comprobando las líneas y reservando la estimación en tu saldo |
failed |
el lote no se ejecutó: errors explica por qué, línea por línea cuando puede; no se facturó nada |
in_progress |
las líneas se están enrutando y enviando a los proveedores |
finalizing |
todas las líneas han terminado; se están escribiendo los archivos de salida y de errores |
completed |
terminado; output_file_id y, cuando alguna línea falló, error_file_id están rellenos |
expired |
terminado, pero algunas líneas no pudieron responderse antes de que acabara la ventana; están en el archivo de errores con batch_expired |
cancelling |
lo cancelaste; se está pidiendo a los proveedores que paren |
cancelled |
terminado tras una cancelación; lo que los proveedores acabaron antes de parar está en el archivo de salida |
El objeto de lote lleva además request_counts (total, completed, failed: las líneas fallidas, caducadas y canceladas juntas) y un objeto zabio con tier: "batch" y cost, los totales acumulados de las líneas facturadas hasta el momento: vendor_micros, billed_micros, direct_micros (las mismas líneas en tu modelo de referencia a su precio por lotes), saved_micros (la suma de los ahorros de las líneas: una línea auto en una key con la referencia pendiente no suma ninguno) y classifier_micros (siempre 0: el clasificador es coste de Zabio).
Descargar
Section titled “Descargar”curl
curl https://api.zabio.ai/v1/files/file_.../content \ -H "Authorization: Bearer inserta aquí tu key de zabio.ai" -o output.jsonlPython
out = client.files.content(b.output_file_id).textfor line in out.splitlines(): r = json.loads(line) print(r["custom_id"], r["response"]["body"]["choices"][0]["message"]["content"], r["response"]["body"]["zabio"]["cost_micros"])if b.error_file_id: print(client.files.content(b.error_file_id).text)Cada línea de salida es {"id": "batch_req_...", "custom_id": "...", "response": {"status_code": 200, "request_id": "req_...", "body": {...}}, "error": null}, y body es la chat completion completa que la misma petición habría devuelto de forma síncrona, con su objeto zabio: model_served, routing, cost (vendor_micros, la comisión, direct_micros, saved_micros), tier ({"requested": "batch", "applied": "batch"}). Cada línea de error es {"id": "batch_req_...", "custom_id": "...", "response": null, "error": {"code": "...", "message": "..."}}; una línea que el proveedor respondió con un error lleva además response.status_code y un sobre de error en response.body. Las líneas vienen en el orden de tu entrada; empareja los resultados con tus peticiones por custom_id, nunca por posición.
Lotes sin archivos
Section titled “Lotes sin archivos”Una petición suelta puede llegar a la cola de lotes sin archivo: envíala a la API de Responses con background: true y un zabio.deadline de 24 horas o más. Zabio agrupa las peticiones en segundo plano de tu organización durante hasta 15 minutos, o hasta que llegan 500, y las envía juntas como un lote del proveedor por modelo, a los precios por lotes, enrutadas línea a línea como un lote de archivo. Cada petición se factura como una línea de lote, el resultado llega dentro de la ventana de 24 horas de los proveedores contada desde el momento del envío, y lo lees con GET /v1/responses/{id}. No hay archivo que subir ni archivo de salida que descargar; estas peticiones no aparecen en GET /v1/batches. Una petición en segundo plano con un modelo cuyo proveedor no tiene nivel de lotes se ejecuta como trabajo propio en flex o estándar, y su zabio.tier lo dice con fallback: "no_batch_tier".
Qué modelos
Section titled “Qué modelos”Cada línea se enruta como una llamada normal y se cobra a los precios por lotes. Dentro de un lote, auto y las líneas eligen solo entre modelos cuyo proveedor tiene API de lotes, así que una línea auto siempre obtiene el precio por lotes. Los modelos con nivel de lotes hoy son todos los modelos de chat de OpenAI salvo chat-latest, todos los modelos de chat de Anthropic, todos los modelos Gemini de Google, todos los modelos de chat de Mistral (las filas Mistral, Codestral y Ministral) y llama-3.3-70b-instruct-turbo de Together. Los precios están en la página de Modelos.
Algunos modelos no tienen nivel de lotes porque su proveedor no publica un precio por lotes para ellos: los demás modelos de Together (qwen3.5-9b, gpt-oss-120b y las copias de DeepSeek en Together) se ejecutan a tarifa estándar en la API de lotes de Together, xAI no publica descuento por lotes para grok-4.7, DeepSeek no tiene API de lotes. Un modelo recibe nivel de lotes el día que su proveedor publica una cifra, nunca antes: Google no imprimía ninguno para gemini-3.5-flash-lite hasta octubre de 2026, y el modelo lo tiene desde el 2026-10-04.
Una línea que nombra un modelo sin nivel de lotes no obtendría el precio por lotes, así que el lote falla al crearse, antes de ejecutar o facturar nada: errors nombra el modelo, los números de línea y las soluciones, usar auto, uno de los modelos con nivel de lotes, o la opción explícita que se describe más abajo. Un descuento sobre miles de llamadas nunca debe desaparecer en silencio.
No disponible en esta versión dentro de un lote: streaming (stream: true se rechaza) y sesiones. Cada línea es su propia petición: cada línea auto se clasifica y paga una vez la cantidad por la elección automática del modelo, como cualquier petición auto.
Una línea de un lote no escribe una caché de prompt propia salvo que la propia línea la pida. En una llamada normal, la caché automática de Zabio coloca marcadores de caché en los modelos de Anthropic cuando es probable que el prompt se repita, para que cada turno de una conversación lea el anterior desde la caché; dentro de un lote cada línea es una petición y nada vuelve a leer su entrada, mientras que la escritura cuesta más que la entrada normal (1.25 veces el precio de entrada en la caché de 5 minutos, 2 veces en la de 1 hora, a las tarifas por lotes). Por eso una línea de un lote viaja al proveedor sin caché, y la caché automática de Zabio dentro de un lote es el prefijo compartido que se describe a continuación. Cuando varias líneas de un lote comparten un prefijo largo en un modelo de Anthropic (el mismo system prompt, las mismas herramientas y los mismos mensajes iniciales, byte a byte, con una pregunta distinta al final de cada línea), Zabio cachea la parte compartida por ti: la primera de esas líneas va sola al proveedor y escribe el prefijo en la caché de 1 hora, y el resto va cuando esa ha terminado, leyendo cada una el prefijo a la tarifa de caché en lugar de pagarlo como entrada nueva. No se escribe nada por la pregunta propia de cada línea. Ocurre a partir de tres líneas que comparten al menos 1,024 tokens (más en los modelos con un mínimo cacheable mayor, 4,096 en claude-haiku-4-5) y le cuesta al lote unos minutos al principio. Medido en lotes reales: todas las líneas después de la primera leyeron el prefijo, así que un lote de 1,000 líneas que comparten 30,000 tokens en claude-sonnet-5 paga por la parte compartida una escritura y 999 lecturas, alrededor de una décima parte del precio sin caché. Una línea que fija zabio.cache.ttl por sí misma conserva su elección y queda fuera del grupo. El zabio.cache de la línea de salida muestra lo que ocurrió en cada línea, y la regla de la tarifa no cambia: Zabio nunca se queda con parte del descuento de la caché.
Líneas que Zabio responde directamente
Section titled “Líneas que Zabio responde directamente”La mayoría de las líneas viajan a la API de lotes de un proveedor y vuelven dentro de la ventana. Tres tipos de líneas las atiende Zabio por su cuenta, y cada una lo dice en su zabio.tier: requested es siempre batch dentro de un lote, applied es lo que sirvió la línea, y fallback explica por qué difieren.
Líneas de clasificación respondidas por Jev. Una línea con auto cuyo response_format es un json_schema de opciones fijas, booleanos y puntuaciones acotadas es una tarea de clasificación, el tipo que Jev responde en auto. Dentro de un lote, Zabio pregunta a Jev al planificar cuando el precio estimado de Jev está por debajo del precio por lotes del modelo de texto que eligió el enrutado; una respuesta aceptada termina la línea al momento, sin esperar a ningún lote del proveedor. La línea se factura como cualquier llamada auto: el precio de Jev más la comisión de servicio y la cantidad por la elección automática del modelo, con el ahorro medido contra el modelo de referencia de tu key a su precio por lotes. Su zabio.tier es {"requested": "batch", "applied": "standard", "fallback": "decision"}, model_served es jev-1.13.0 y zabio.decision lleva las probabilidades. Cuando la respuesta de Jev no es lo bastante segura, o Jev falla, la línea va al lote del proveedor como cualquier otra y la llamada a Jev corre por nuestra cuenta.
La división inteligente de peticiones. Una línea con auto que lleva una fuente grande y una pregunta recibe el mismo trato que una llamada normal: Zabio pregunta a Jev si la petición se puede destilar, calcula el precio de los dos caminos usando el precio por lotes de la respuesta, extrae las líneas relevantes con un modelo barato al precio estándar, comprueba el extracto con Jev, y después manda la respuesta al lote sobre el extracto, al precio por lotes. El zabio.split de la línea de salida lista cada paso (preguntas, extracto, comprobación, respuesta) con su modelo y su coste; el extracto se factura a coste del proveedor y la petición paga una sola comisión. Si el modelo responde con la línea reservada pidiendo la fuente completa, la respuesta sobre el extracto y el extracto corren por nuestra cuenta, no se factura nada por ellos, y la línea se ejecuta de nuevo sobre la fuente completa en una segunda vuelta cuando quedan al menos dos horas antes de que acabe la ventana; si no, termina en el archivo de errores con batch_expired. zabio.split: "off" en el cuerpo de una línea desactiva la división inteligente de peticiones para esa línea, como en una llamada normal.
Líneas al precio estándar, cuando tú lo pides. Activa zabio.allow_standard_price: true en el lote y una línea que nombra un modelo sin nivel de lotes ya no hace fallar el lote: Zabio la ejecuta como una llamada síncrona normal desde el proceso de lotes al precio estándar, con el mismo respaldo del mismo proveedor y los mismos reintentos que cualquier llamada, y la línea dice {"requested": "batch", "applied": "standard", "fallback": "no_batch_tier"}. Es siempre tu elección, nunca el valor por defecto. Una línea que nombra deepseek-flash o deepseek-v4-pro con esta opción espera a las horas valle de DeepSeek, cuatro horas como máximo y siempre dentro de la ventana de 24 horas, y después se ejecuta al precio valle, la mitad del precio de lista, que DeepSeek aplica por su cuenta; si la espera no cabe antes de que acabe la ventana, la línea se ejecuta al momento. Las líneas al precio estándar se ejecutan de pocas en pocas desde el proceso de lotes, así que un lote con miles de ellas tarda más que un lote del proveedor del mismo tamaño; la ventana de 24 horas sigue valiendo.
Dinero
Section titled “Dinero”Cuando se acepta un lote, Zabio estima su coste y reserva ese importe en tu saldo: la retención. La estimación usa la entrada de cada línea, su max_tokens (o el valor por defecto) y el precio del modelo en el que se ejecutará; tras el enrutado pasa a ser el coste planificado. Las demás llamadas no pueden gastar lo que necesita un lote en marcha. Si la estimación supera tu saldo disponible, el lote falla al crearse con insufficient_credits y la estimación, para que puedas añadir créditos o enviar un lote más pequeño; no se factura nada.
Cada línea se factura cuando llega su resultado, al precio por lotes del proveedor por los tokens que usó, más las comisiones habituales: la comisión de servicio y, en una línea auto, la cantidad por la elección automática del modelo (consulta Facturación). El ahorro se mide contra el modelo de referencia de tu key a su precio por lotes (o a su precio estándar cuando ese modelo no tiene nivel de lotes), porque eso es lo que habrías pagado allí: nunca nos quedamos con una parte del descuento del proveedor. A medida que se facturan líneas la retención se reduce, y lo que queda se libera cuando termina el lote. La factura son los tokens reales; la retención era solo la estimación. Las líneas que Zabio responde directamente siguen la misma regla: una línea al precio estándar se estima al precio estándar, y una línea que respondió Jev o que pasó por la división inteligente de peticiones devuelve su parte de la retención cuando termina. Un lote en marcha nunca se detiene por dinero: si el coste real supera la estimación, las líneas se responden y se facturan igualmente, y tu saldo puede quedar ligeramente por debajo de cero, como puede ocurrir con una sola llamada. Hay un ejemplo con números en Facturación.
Límites
Section titled “Límites”| Límite | Valor |
|---|---|
| Tamaño del archivo | 100 MiB |
| Líneas por lote | 50,000 |
| Lotes abiertos por organización | 20 a la vez |
| Tamaño de una línea | 10 MiB |
| Endpoint | solo /v1/chat/completions |
| Ventana | solo 24h |
Las líneas se validan al subir el archivo (JSON, un custom_id único, method POST, una url, un objeto body) y de nuevo al crear el lote (los campos de la petición, el modelo, las reglas de la key).
Cancelar
Section titled “Cancelar”POST /v1/batches/{id}/cancel cancela de inmediato las líneas que aún no se enviaron a un proveedor y pide a los proveedores que paren el resto. Lo que terminen antes de parar se conserva, se factura y se escribe en el archivo de salida; el lote termina cancelled. Un lote que ya ha terminado responde batch_not_cancellable.
Python
client.batches.cancel(b.id)Qué ve cada key
Section titled “Qué ve cada key”Una key que pertenece a un proyecto solo lista, lee, cancela y borra los archivos y lotes creados con keys de ese proyecto, y solo crea un lote a partir de uno de esos archivos; una key a nivel de la organización llega a toda la organización. Un archivo o lote de fuera responde 404 not_found, como si no existiera. Consulta Organizaciones, miembros y keys.
Qué se conserva, y durante cuánto tiempo
Section titled “Qué se conserva, y durante cuánto tiempo”El archivo de entrada, el de salida y el de errores se guardan cifrados por organización, como las conversaciones guardadas, durante el periodo de retención de tu organización (30 días por defecto, de 1 a 365), contado desde la subida para las entradas y desde el final del lote para los resultados, o hasta que los borres con DELETE /v1/files/{id}, o hasta la caducidad que fijaste al subirlos con expires_after, lo que ocurra antes. Un archivo que un lote en marcha todavía necesita nunca se borra; borrarlo responde file_in_use hasta que el lote termina. Zabio no conserva copias de las peticiones ni de los resultados fuera de esos archivos, y borra las copias que guarda el proveedor en cuanto recoge los resultados. Cada línea va al proveedor al que se enruta bajo los términos de la API de ese proveedor, como una petición normal. Consulta Privacidad.
Los objetos de lote y sus recuentos siguen apareciendo en GET /v1/batches cuando los archivos ya no existen.
Errores
Section titled “Errores”| Estado | code | Cuándo |
|---|---|---|
| 400 | invalid_file |
el archivo subido no es JSONL de lotes válido; el mensaje nombra la línea |
| 400 | invalid_value |
endpoint no es /v1/chat/completions, completion_window no es 24h, metadata es demasiado grande, o el archivo no es una subida procesada con purpose batch |
| 400 | batch_too_large |
el archivo tiene más de 50,000 líneas |
| 400 | batch_not_cancellable |
el lote ya ha terminado |
| 404 | not_found |
no hay archivo ni lote con ese id |
| 409 | file_in_use |
un lote en marcha todavía necesita el archivo que intentaste borrar |
| 413 | file_too_large |
la subida supera los 100 MiB |
| 429 | too_many_open_batches |
tu organización tiene 20 lotes abiertos |
Un lote también puede fallar después de crearse, con el motivo en su lista errors y sin facturar nada:
| code | Cuándo |
|---|---|
model_not_batchable |
una línea nombra un modelo sin nivel de lotes y el lote no activó zabio.allow_standard_price (el mensaje lista los modelos con nivel de lotes y la opción) |
model_not_found |
una línea nombra un modelo que no existe o que la key no puede usar |
no_batch_model |
no hay ningún modelo con nivel de lotes activado para tu organización |
insufficient_credits |
la estimación supera tu saldo disponible; el mensaje lleva las dos cifras |
invalid_url, invalid_request, missing_messages, invalid_json y los demás códigos de petición |
una línea no es una petición de chat completions válida |
api_key_revoked |
la key que creó el lote fue revocada |
empty_file, input_file_missing |
el archivo no tiene líneas, o se borró antes de que el lote se ejecutara |
Y una línea suelta puede terminar en el archivo de errores con uno de estos códigos:
| code | Cuándo |
|---|---|
batch_expired |
el proveedor no pudo responder la línea antes de que acabara la ventana |
batch_cancelled |
cancelaste antes de que la línea se ejecutara |
vendor_rejected |
el proveedor rechazó la petición (un parámetro no válido para ese modelo, por ejemplo) |
vendor_batch_failed |
el proveedor no pudo ejecutar el grupo en el que iba la línea antes de que acabara la ventana |
no_eligible_model |
ningún modelo con nivel de lotes pudo atender la línea con las reglas de la key |
byok_key_rejected |
una línea al precio estándar se ejecutó con la key propia de tu organización para ese proveedor y el proveedor rechazó esa key |
transient_rate_limit, transient_5xx, network_failure, persistent_billing, schema_violation |
el proveedor respondió la línea con esa clase de error y la ventana acabó antes de un reintento |