Ir al contenido
Zabio está en beta: los modelos, los precios y las funciones pueden cambiar. Cuéntanos qué falla en hello@zabio.ai.

Responses API y conversaciones

POST /v1/responses implementa el formato de la Responses API de OpenAI, así que los SDK de OpenAI funcionan sin cambios. A diferencia de chat completions, que no guarda nada, una response se guarda en el lado de Zabio junto con la conversación a la que pertenece. Tú envías el mensaje nuevo; Zabio monta el historial, lo enruta, guarda la respuesta y te la devuelve.

Para la forma exacta de cada petición y respuesta, con ejemplos en curl, Python y Node, consulta la Referencia de la API.

La key necesita el scope conversations (las keys lo tienen por defecto). Una key sin él recibe 403 insufficient_scope.

from openai import OpenAI
client = OpenAI(base_url="https://api.zabio.ai/v1", api_key="insert your zabio.ai key here")
first = client.responses.create(model="auto", input="Me llamo Ana. Recuérdalo.")
second = client.responses.create(model="auto", input="¿Cómo me llamo?", previous_response_id=first.id)
print(second.output_text)
Campo Tipo Notas
model cadena un id del catálogo o auto, auto/best, auto/balanced, auto/cheap. Si se omite, es auto
input cadena o lista de items una cadena es un mensaje de usuario. Items: {"role": "user" o "assistant" o "system" o "developer", "content": cadena o partes}, {"type": "function_call_output", "call_id", "output"}, {"type": "function_call", "call_id", "name", "arguments"}. Partes de contenido: input_text, input_image (image_url)
instructions cadena instrucciones de sistema para este turno (se guardan con el turno)
previous_response_id resp_... continúa desde esa response. Apuntar a una response antigua crea una rama: la conversación conserva su hilo y esta response empieza uno nuevo
conversation conv_... o {"id": ...} continúa desde el último turno de la conversación. Con previous_response_id, ambos deben pertenecer a la misma conversación
store booleano, por defecto true false calcula el turno a partir del historial guardado y no guarda nada. El id de la response no se puede recuperar después
tools, tool_choice herramientas de tipo función: {"type": "function", "name", "description", "parameters"}; tool_choice es auto, none, required o {"type": "function", "name"}
max_output_tokens, temperature, top_p, parallel_tool_calls muestreo; max_output_tokens es 4096 por defecto. Un valor por encima de la salida máxima del modelo servido se baja a ese límite, en cada intento
text.format {"type": "json_object"} o {"type": "json_schema", "name", "schema", "strict"}. Aplica la advertencia sobre json_object en Anthropic de Chat completions
reasoning.effort none, minimal, low, medium, high o xhigh, aplicado al control propio del modelo servido; zabio.reasoning indica lo que se aplicó
metadata objeto hasta 16 claves, claves de 64 caracteres, valores de 512 caracteres
stream booleano server sent events, ver más abajo
background booleano, por defecto false ejecuta el turno más tarde y devuelve al momento la response con status: "queued"; consulta GET /v1/responses/{id} para leer el resultado. Necesita store true y no admite stream. Ver Responses en segundo plano
zabio objeto session_id, vendors, fallback, cache, split, deadline como en chat completions, más retention_days (de 0 a 365, para una conversación creada por esta llamada) y compaction (auto u off)

Sin previous_response_id ni conversation se crea una conversación nueva para la response cuando store es true.

{
"id": "resp_...", "object": "response", "created_at": 1758283200,
"status": "completed",
"model": "claude-haiku-4-5",
"output": [
{"type": "message", "id": "msg_...", "status": "completed", "role": "assistant",
"content": [{"type": "output_text", "text": "...", "annotations": []}]},
{"type": "function_call", "id": "fc_...", "call_id": "call_...", "name": "get_weather", "arguments": "{\"city\":\"Lima\"}", "status": "completed"}
],
"usage": {"input_tokens": 120, "input_tokens_details": {"cached_tokens": 100}, "output_tokens": 20, "output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 140},
"previous_response_id": null,
"conversation": {"id": "conv_..."},
"metadata": {}, "store": true,
"zabio": {"vendor": "anthropic", "model_served": "claude-haiku-4-5", "routing": {...}, "cost_micros": 63, "cost": {...}, "byok": false, "escalated_from": "", "compacted": false, "split": {...}}
}

status es completed, incomplete (la salida llegó a max_output_tokens; incomplete_details.reason lo indica; un modelo de razonamiento que gastó todo su presupuesto pensando termina así sin texto: sube max_output_tokens para que deje sitio a la respuesta por encima del razonamiento, o baja el esfuerzo de razonamiento) o failed; una response en segundo plano es también queued o cancelled, y lleva background: true. usage.output_tokens_details.reasoning_tokens es el recuento de razonamiento del proveedor cuando lo informa, si no 0. El objeto zabio es el que se describe en Chat completions, y aquí importan tres campos más:

  • escalated_from: se rellena cuando el primer modelo elegido por auto pidió uno más potente, o cuando su respuesta no pasó la comprobación de Zabio, y la respuesta vino de otro modelo. El escalado en sí nunca aparece en output.
  • compacted: se rellena cuando el historial guardado se resumió antes de este turno porque ya no cabía en la ventana de contexto del modelo.
  • split: lo que hizo división y enrutado con este turno (ver División y enrutado), guardado con la response igual que compacted. Solo se puede dividir el último mensaje del usuario del propio turno, nunca el historial guardado; zabio.split: "off" envía el turno entero.
  • tier: el nivel de servicio de este turno (ver Nivel flex): requested, applied y fallback, guardado con la response igual que split. Un zabio.deadline de 15 minutos o más hace que el turno pueda servirse en el nivel flex del proveedor; uno de 24 horas o más pide el nivel de lotes, que solo una response en segundo plano puede esperar. En un turno síncrono con ese plazo applied es flex o standard y fallback es synchronous.
  • plan: presente en un turno servido con los límites de Free (consulta Facturación, “El plan Free”): tier (free), reason (free_plan o zero_balance), paid_would_use (con auto, el modelo que habría recibido una cuenta de pago, cuando es distinto) y topup_url. Una response en segundo plano lo lleva en el objeto en cola y en el final; un turno admitido con saldo se factura como de pago hasta el final aunque el saldo cruce el cero mientras se ejecuta.

Envía background: true y la llamada devuelve al momento el objeto response con status: "queued", background: true y un output vacío. El turno se ejecuta más tarde; consulta GET /v1/responses/{id} hasta que el estado sea completed, incomplete, failed o cancelled, y lee entonces output, usage y zabio como en cualquier response. Todo lo demás del turno es igual: la conversación, previous_response_id, las herramientas, la compactación, las opciones zabio. background necesita store true (el valor por defecto; la response se guarda para poder consultarla) y no se puede combinar con stream.

Dónde se ejecuta el turno depende de zabio.deadline:

  • Un plazo de 24 horas o más con auto o con un modelo que tiene nivel de lotes mete el turno en el siguiente lote de tu organización: las peticiones en segundo plano sueltas se agrupan durante hasta 15 minutos, o hasta que llegan 500, y se envían juntas como un lote del proveedor por modelo a los precios por lotes de los proveedores, la mitad del precio de lista más o menos. El resultado llega dentro de la ventana de 24 horas de los proveedores contada desde ese momento, normalmente mucho antes. El turno se factura exactamente como una línea de un lote: el precio por lotes por los tokens que usó, más las comisiones habituales, con el ahorro medido contra tu modelo de referencia a su precio por lotes. zabio.tier es {"requested": "batch", "applied": "batch"}. Antes de encolar el turno, su coste estimado se reserva en tu saldo como el de un lote; si la estimación supera tu saldo disponible, la llamada responde 402 insufficient_credits y no se guarda nada.
  • Cualquier otra petición en segundo plano se ejecuta en cuanto un worker la toma, con el mismo enrutado que una llamada síncrona: en el nivel flex a partir de un plazo de 15 minutos cuando el modelo lo tiene, en estándar si no. Un plazo de 24 horas o más con un modelo cuyo proveedor no tiene nivel de lotes se ejecuta también así, y lo dice: zabio.tier es {"requested": "batch", "applied": "flex" (o "standard"), "fallback": "no_batch_tier"}. Nada pasa en silencio a un nivel más caro.

El objeto en cola ya lleva zabio.tier con el nivel que recibió la petición; el objeto final lleva el que la sirvió.

Cancelar. POST /v1/responses/{id}/cancel termina un turno en cola, o uno que todavía espera a que se envíe su lote, con status: "cancelled" y sin facturar nada; sus entradas se quedan en la conversación con ese estado, como las de un turno fallido. Un turno que ya se está ejecutando como trabajo se corta y termina cancelled. Un turno ya enviado al proveedor del modelo dentro de un lote no se puede recuperar: la llamada responde 409 not_cancellable y la respuesta llega y se factura. Cancelar un turno que ya terminó devuelve el objeto tal cual; cancelar una response que no se creó con background responde 400 not_background.

Python

r = client.responses.create(model="auto", input="Resume este informe: ...", background=True,
extra_body={"zabio": {"deadline": "24h"}})
print(r.status) # queued
while r.status in ("queued", "in_progress"):
time.sleep(60)
r = client.responses.retrieve(r.id)
print(r.status, r.output_text, r.model_extra["zabio"]["tier"])

Node

let r = await client.responses.create({ model: "auto", input: "Resume este informe: ...", background: true,
zabio: { deadline: "24h" } });
while (r.status === "queued" || r.status === "in_progress") {
await new Promise((ok) => setTimeout(ok, 60_000));
r = await client.responses.retrieve(r.id);
}
console.log(r.status, r.output_text, r.zabio.tier);

Con stream: true la respuesta es un stream de server sent events: response.created, response.in_progress, response.output_item.added, response.content_part.added, response.output_text.delta, response.output_text.done, response.content_part.done, response.output_item.done, response.function_call_arguments.delta, response.function_call_arguments.done, y por último response.completed, response.incomplete o response.failed con el objeto response completo. Cada evento lleva sequence_number. Si el proveedor falla se envía un evento error.

const stream = await client.responses.create({ model: "auto", input: "Hola", stream: true });
for await (const event of stream) {
if (event.type === "response.output_text.delta") process.stdout.write(event.delta);
if (event.type === "response.completed") console.log("\n", event.response.conversation.id, event.response.zabio);
}
Estado code Cuándo
400 invalid_value en background background con stream, o con store: false
400 background_unavailable las responses en segundo plano no están disponibles en este servidor
400 not_background cancelar una response que no se creó con background
409 not_cancellable cancelar un turno ya enviado al proveedor del modelo dentro de un lote; la respuesta llega y se factura
400 conversation_mismatch previous_response_id pertenece a otra conversación
400 conversation_too_large la conversación llegó a 4,000 entradas o 2,000,000 de tokens estimados en su hilo; empieza una nueva
400 context_length_exceeded el historial más este turno no cabe en la ventana de contexto del modelo y la compactación está desactivada o no puede ayudar; el mensaje incluye las cifras
403 conversations_closed las conversaciones guardadas están cerradas para esta organización
404 previous_response_not_found, conversation_not_found, response_not_found desconocida o borrada
503 conversations_unavailable las conversaciones guardadas no están disponibles en este servidor

También aplica todo lo de Errores, incluido el 402 insufficient_credits y los códigos free_tier_* del plan Free.

  • GET /v1/responses/{id} devuelve una response guardada con su salida, o su estado actual si es una response en segundo plano que todavía se ejecuta.
  • POST /v1/responses/{id}/cancel cancela una response en segundo plano.
  • DELETE /v1/responses/{id} elimina la response con su turno (las entradas que guardó y las salidas) y todo lo guardado después en el mismo hilo. La conversación continúa desde donde empezaba ese turno. Borrar una response en segundo plano en cola la saca de la cola.
  • POST /v1/conversations con {"metadata": {...}, "items": [...], "zabio": {"retention_days": 30}} crea una conversación vacía (o con contenido inicial).
  • GET /v1/conversations/{id} devuelve {"id", "object": "conversation", "created_at", "metadata", "zabio": {"entries", "token_estimate", "expires_at", "last_turn_at", "retention_days"}}.
  • POST /v1/conversations/{id} con {"metadata": {...}} sustituye los metadatos.
  • DELETE /v1/conversations/{id} borra la conversación y todo lo que contiene, de forma inmediata y permanente.
  • GET /v1/conversations/{id}/items?limit=20&after=msg_...&order=asc lista todos los items guardados en orden de creación (todos los hilos), incluidos los resúmenes de compactación como {"type": "compaction", "summary": ...}.
  • POST /v1/conversations/{id}/items con {"items": [...]} añade items sin un turno de modelo, por ejemplo para cargar historial previo.

Una key que pertenece a un proyecto solo lee, continúa, cambia y borra las responses y conversaciones creadas con keys de ese proyecto; una key a nivel de la organización llega a toda la organización. Una response o conversación de fuera responde 404 not_found, como si no existiera. El proyecto es aquel en el que está ahora la key que la creó. Consulta Organizaciones, miembros y keys.

En los turnos auto el modelo puede indicar que la tarea supera su capacidad. Zabio repite entonces el mismo turno en el siguiente modelo de la curva, uno que su conocimiento dice que es probablemente mejor, con todo el historial y devuelve esa respuesta; los turnos siguientes se quedan en el modelo más potente. Dentro de una conversación auto clasifica cada mensaje nuevo del usuario y reutiliza esa tarea en los turnos de resultado de herramienta que siguen, hasta que una señal (una racha de llamadas a herramientas fallidas, una pausa larga, una clasificación con poca confianza, una queja del usuario) provoca una nueva elección.

Cuando el historial guardado más un turno nuevo no caben en la ventana de contexto del modelo que sirve la petición, Zabio resume la parte más antigua del historial con un modelo rápido, guarda el resumen como un item de compactación y continúa. La llamada del resumen se ejecuta con la key de Zabio, se factura como cualquier otra y aparece entre las responses de la conversación; por eso un turno que requiere compactación necesita crédito aunque nombre un modelo servido con tu propia key, y con saldo cero se ejecuta con los términos de Free como cualquier llamada auto, o recibe 402 insufficient_credits antes de que se haga ningún resumen cuando la organización está configurada para detenerse. zabio.compaction: "off" lo desactiva; la petición falla entonces con context_length_exceeded y las cifras.

Un modelo potente puede pensar minutos antes de su primer token. Zabio permite que un intento con el proveedor dure hasta 600 segundos en los modelos powerful y frontier (180 segundos en los demás) y mantiene viva la conexión mientras tanto.

  • Sin streaming. Tras 90 segundos sin respuesta, Zabio confirma el estado 200 y las cabeceras y escribe un espacio cada 20 segundos hasta que llega el objeto response; los parsers de JSON ignoran los espacios. Un fallo a partir de ese momento se entrega como el objeto response con status: "failed" y un objeto error (code, message), la forma que tiene de todos modos una response fallida. Las llamadas respondidas antes de 90 segundos no cambian, códigos de estado incluidos.
  • Con streaming. Desde unos 20 segundos, antes de response.created si el proveedor aún no ha abierto, y siempre que el proveedor calla durante 20 segundos, Zabio envía una línea de comentario SSE (: keepalive), que los clientes SSE ignoran. Un fallo antes de cualquier salida viene como un evento response.failed con el objeto en status: "failed" y su error.
  • Tu propio timeout. Pon el timeout de petición de tu cliente en al menos 600 segundos para los modelos powerful y frontier.
  • Si te desconectas. Cerrar la conexión cancela la llamada al proveedor. Una llamada sin streaming no te cuesta nada; un stream se factura por los tokens entregados antes de la desconexión. Un turno guardado que se cortó queda marcado como failed o incomplete.
  • Cabeceras en una llamada mantenida viva. Las cabeceras x-zabio-* se enviaron antes de elegir el proveedor; el proveedor, el modelo servido y el enrutado están en el campo zabio del objeto.

Cada organización tiene una retención por defecto (30 días, ajustable de 1 a 365 en Ajustes). Una conversación puede fijar un retention_days más corto o más largo (de 0 a 365) al crearse; 0 la borra en cuanto se devuelve la response. El reloj de retención se reinicia en cada turno. Las conversaciones caducadas se borran cada noche. Borrar a través de la API es inmediato y permanente. Los cuerpos se cifran en reposo con una clave que pertenece a tu organización; cerrar la organización borra las conversaciones y destruye la clave. Consulta Privacidad.

Registro de cuerpos en llamadas sin estado

Section titled “Registro de cuerpos en llamadas sin estado”

/v1/chat/completions y /v1/embeddings no guardan nada salvo que la API key tenga activado el registro de cuerpos (un ajuste de la key, desactivado por defecto). Con él activado, la petición y la respuesta de cada llamada se guardan cifradas durante el periodo de retención de la organización, y los propietarios y administradores pueden leerlas desde la aplicación web. El id de la llamada es el id de la response sin el prefijo chatcmpl-, escrito como call_....