Cuando algo falla

Buscar un error

#Pega el error

Lee los errores en este orden: estado HTTP, luego error.type y después error.code. El estado siempre tiene sentido; type te dice quién rechazó la llamada; code solo es estable en los errores que generó el propio gateway.

#Cómo decidir

  1. 1

    Decide según el estado

    402 significa recargar, 403 significa alcance de la clave, 429 significa esperar, 5xx significa reintentar. Esta capa nunca miente y nunca cambia.

  2. 2

    Revisa error.type

    xiaoxiang_error es nuestro. Cualquier otro — openai_error, claude_error, gemini_error — es el cuerpo propio del proveedor, reenviado sin cambios.

  3. 3

    Después, lee error.code

    Decide según code solo en xiaoxiang_error. Los códigos upstream pertenecen al proveedor y pueden cambiar sin aviso.

No todos los fallos tienen cuerpo JSON

Los rechazos por límite de tasa y los cuerpos demasiado grandes terminan solo con la línea de estado. Un parser que asume que response.json() siempre funciona lanzará una excepción en la ruta de error en lugar de gestionarla.
Envoltorios de error

A tu cliente llegan cinco formatos. Gestiona los cinco — los tres últimos son los que las integraciones suelen pasar por alto.

Error del gateway

El propio gateway rechazó la llamada — autenticación, cuota, enrutamiento, formato de la solicitud.

http
{
  "error": {
    "message": "insufficient account balance, please top up to continue (request id: 20260910...)",
    "type": "xiaoxiang_error",
    "code": "insufficient_user_quota"
  }
}

Error del proveedor upstream, reenviado tal cual

El proveedor respondió con su propio error. El cuerpo se reenvía sin cambios, así que type y code son los del proveedor.

http
{
  "error": {
    "message": "This model's maximum context length is 128000 tokens.",
    "type": "invalid_request_error",
    "code": "context_length_exceeded"
  }
}

Rechazo de un control previo

La idempotencia y algunos otros controles previos al relay responden antes de que exista el envoltorio del relay. Formato plano, code en mayúsculas.

http
{
  "code": "IDEMPOTENCY_IN_PROGRESS",
  "error": "a request with this Idempotency-Key is still processing"
}

Rechazo de tarea asíncrona

Cada endpoint de tareas — video, tarea de imagen, música, 3D — informa los fallos con su propio formato plano. Aquí no hay un objeto error anidado, así que un parser escrito para /v1/chat/completions lee null.

http
{
  "code": "task_not_exist",
  "message": "task does not exist",
  "data": null
}

Solo código de estado, cuerpo vacío

Los rechazos por límite de tasa y los cuerpos de solicitud demasiado grandes terminan solo con el código de estado.

http
HTTP/1.1 429 Too Many Requests
Retry-After: 20
Content-Length: 0
Encabezados de respuesta

Registra el ID de solicitud de cada respuesta, no solo de los fallos. Es el identificador con el que soporte rastrea una llamada, y marca la diferencia entre una respuesta en cinco minutos y un día de suposiciones.

EncabezadoCuándo se envíaQué hacer con él
X-Oneapi-Request-IdCada respuesta, sea correcta o de error.Regístralo junto a tu propio trace id. Soporte responde los tickets a partir de este valor, y el mismo id se añade a los mensajes de error del gateway.
Retry-AfterEn 429, y en 409 mientras una Idempotency-Key aún se está procesando.Espera al menos esta cantidad de segundos. Es la única indicación de backoff que envía el gateway.
X-Idempotent-ReplayedSe reproduce una respuesta almacenada cuando se repite una Idempotency-Key.true significa que no se ejecutó trabajo nuevo y no se volvió a cobrar nada — trata el cuerpo como el resultado original.
X-Idempotency-SealedLa respuesta original no se puede reproducir de forma segura (por ejemplo, un stream o un cuerpo demasiado grande).Deja de reintentar con esa clave. En su lugar, concilia desde el endpoint de la tarea o con tus propios registros.
log-the-request-id.ts
const response = await fetch(url, init);
const requestId = response.headers.get("X-Oneapi-Request-Id");

if (!response.ok) {
  // Bodies are absent on 429 and 413 — never assume JSON here.
  const detail = await response.text();
  logger.error({ status: response.status, requestId, detail });
  throw new GatewayError(response.status, requestId, detail);
}

#Tabla de códigos de error

Errores que genera el propio gateway, siempre con type "xiaoxiang_error". Dos filas comparten el estado 429 y significan cosas opuestas — lee el code antes de aplicar backoff.

HTTPCódigoSignificadoQué hacer¿Reintentar?
401ningunoBearer token ausente, mal formado, eliminado o desactivado. Esta capa no define code — compara por el estado.Revisa el encabezado Authorization y el estado de la clave en Consola → Claves de API.no
401signature_requiredLa clave tiene activada la firma de solicitudes y a la solicitud le falta X-Timestamp, X-Nonce o X-Signature.Firma cada solicitud hecha con esta clave (Autenticación y claves → Firma de solicitudes), o desactiva la firma en la clave.no
401signature_expiredX-Timestamp no está en segundos Unix de 10 dígitos o difiere más de 5 minutos de la hora del servidor. La respuesta incluye X-Server-Timestamp.Corrige el reloj del cliente según la diferencia con X-Server-Timestamp y vuelve a firmar con un nonce nuevo.sí
401signature_invalidX-Signature no coincide con el secreto de firma de la clave, o X-Nonce está mal formado.Firma el timestamp, el nonce, el método y la ruta (sin query string), unidos por saltos de línea, con el secreto actual de Consola → Claves de API.no
401signature_replayedEste X-Nonce ya se usó con esta clave en los últimos 10 minutos.Genera un nonce aleatorio nuevo para cada solicitud, incluidos los reintentos.sí
403access_deniedLa IP del cliente está fuera de la lista de IP permitidas de la clave.Añade la dirección de salida en Consola → Claves de API, o quita la lista de IP permitidas en las claves sin IP fija.no
403ningunoCuenta suspendida, o la clave no tiene permiso para usar este modelo o canal.Revisa primero la lista de modelos permitidos de la clave; si no tiene restricciones, abre un ticket — la cuenta en sí está bloqueada.no
400unsupported_endpointEl modelo existe, pero no atiende esta ruta. El mensaje indica los endpoints que sí atiende.Mueve la llamada a uno de los endpoints indicados — los modelos de tareas de imagen no responden en /v1/chat/completions.no
400read_request_body_failedNo se pudo leer el cuerpo de la solicitud — subida truncada, Content-Type incorrecto o un stream que terminó antes de tiempo.Envía un cuerpo completo con un Content-Type que coincida. Reenviar la misma solicitud defectuosa falla de la misma forma.no
402insufficient_user_quotaEl saldo de la billetera no cubre el importe reservado previamente para esta llamada.Recarga en Consola → Billetera y vuelve a intentarlo.no
429insufficient_user_quotaSe alcanzó el límite de gasto diario de la cuenta. Mismo estado que la limitación de tasa, significado opuesto — el backoff no lo resolverá.Sube o quita el límite en la configuración de la cuenta, o continúa mañana. Decide según el code, no solo por el 429.no
429ningunoLimitación RPM/RPD por clave o un control de concurrencia. El cuerpo está vacío; Retry-After indica la espera.Espera Retry-After segundos y luego reintenta con backoff exponencial y jitter.sí
413read_request_body_failedEl cuerpo de la solicitud supera el tamaño aceptado. El límite a nivel de transporte termina con un cuerpo vacío.Reduce el payload — sube los archivos mediante /v1/media/upload-policy y envía la URL en lugar de Base64.no
422invalid_requestNingún canal admite esta intención multimedia para el modelo — por ejemplo, imagen a video en una ruta que solo hace texto a video.Consulta las intenciones admitidas en la ficha del modelo y cambia de modelo o de intención.no
503model_not_foundNombre de modelo desconocido, o ningún canal sano lo atiende ahora mismo. Fíjate en el estado: un modelo desconocido devuelve 503, no 404.Verifica el nombre con /v1/models y reintenta — el failover suele encontrar un canal sano en el siguiente intento.sí
500sensitive_words_detectedEl prompt activó el filtro de contenido antes de salir del gateway.Cambia el prompt. Reintentar el mismo texto siempre falla.no
500count_token_failedEl gateway no pudo estimar los tokens para calcular el precio, normalmente por un array de mensajes mal formado.Valida el array messages; si está bien formado, reintenta y abre un ticket con el ID de solicitud.sí
500invalid_requestNo se pudo interpretar la solicitud con un formato que acepte este endpoint. Fíjate en el estado — un cuerpo que el gateway no puede leer se informa como 500, no como 400.Valida el JSON con la referencia del endpoint antes de reintentar.no
502do_request_failedNo se pudo contactar con el proveedor, o este devolvió un fallo que el gateway no pudo recuperar mediante failover.Reintenta con backoff — el siguiente intento suele ir a otro canal.sí
504do_request_failedEl proveedor no respondió dentro del plazo del gateway.Reintenta con backoff; los prompts largos y los proveedores saturados agotan el tiempo de espera con más frecuencia.sí
499do_request_failedTu cliente cerró la conexión antes de que llegara la respuesta — normalmente porque saltó tu propio timeout HTTP a mitad del stream.Aumenta el timeout de lectura del cliente. Las generaciones largas necesitan minutos, no el valor predeterminado de la mayoría de las librerías HTTP.no
Errores del control de idempotencia

Provienen del control de reproducción situado delante del relay, así que usan el formato plano {code, error} con un code en mayúsculas en lugar del envoltorio de OpenAI.

HTTPCódigoSignificadoQué hacer¿Reintentar?
400IDEMPOTENCY_STREAM_UNSUPPORTEDSe envió Idempotency-Key en una solicitud con streaming.Quita el encabezado en las llamadas con streaming, o usa stream:false cuando necesites protección contra repeticiones.no
400IDEMPOTENCY_BODY_UNREADABLENo se pudo calcular el hash del cuerpo para el registro de idempotencia.Envía un cuerpo JSON o multipart normal — se rechazan los cuerpos chunked que no se pueden almacenar en búfer.no
409IDEMPOTENCY_PAYLOAD_CONFLICTEsta clave ya se usó con otro cuerpo, otra ruta u otra query.Genera una clave nueva. Una clave identifica una solicitud exacta, no un bucle de reintentos.no
409IDEMPOTENCY_IN_PROGRESSLa primera solicitud con esta clave sigue en ejecución.Espera el Retry-After (2s) y reintenta con la misma clave — obtendrás la respuesta original.sí
409IDEMPOTENCY_RESULT_UNREPLAYABLELa solicitud original terminó, pero su respuesta quedó sellada y no se puede reproducir.No reenvíes a ciegas. Concilia primero desde el endpoint de la tarea o con tus propios registros.no
503IDEMPOTENCY_UNAVAILABLEEl registro de idempotencia no está disponible, así que el control falla en modo cerrado en lugar de arriesgarse a un cobro doble.Reintenta con backoff — la solicitud no se ejecutó.sí

Cómo funcionan las claves de idempotencia →

Errores de tareas asíncronas

Los endpoints de video, tareas de imagen, música y 3D responden con su propio formato plano — {code, message, data} — tanto al enviar como al consultar o cancelar. Una peculiaridad que hay que prever: consultar un id de tarea desconocido da 400; cancelarlo da 404.

HTTPCódigoSignificadoQué hacer¿Reintentar?
400task_not_existConsultar o hacer remix de un id de tarea que no existe o que pertenece a otra cuenta. Fíjate en el estado — consultar una tarea desconocida da 400, mientras que cancelarla da 404.Guarda task_id al enviar la tarea y consulta exactamente con ese valor.no
400invalid_requestFalta un campo obligatorio — prompt en un remix, task_id en una consulta.Corrige el cuerpo o la ruta antes de reintentar.no
400task_channel_disableUna tarea derivada (remix, extend) está fijada al canal de la tarea original, y ese canal ahora está desactivado.Envía una tarea nueva en lugar de derivarla de la anterior.no
400invalid_source_taskLa tarea de origen no es del tipo adecuado para esta operación — por ejemplo, un id de video pasado a una continuación 3D.Comprueba que el id de origen proviene de la misma familia de capacidades.no
404task_not_existCancelar un id de tarea que no existe o que no es tuyo.No hay nada que cancelar — trátala como ya eliminada.no
409task_not_cancellableLa tarea ya llegó a un estado final, así que no hay nada que cancelar.Consulta una vez más y usa el resultado que ya pagaste.no
501task_cancel_not_supportedEl proveedor de esta tarea no ofrece una API de cancelación. La mayoría no la tiene — la cancelación es la excepción, no la regla.Deja que la tarea termine. Diseña la UI para que la cancelación sea de mejor esfuerzo, no una garantía.no
502cancel_task_failedNo se pudo contactar con el proveedor al cancelar. La tarea no cambia.Reintenta una vez y, si falla, deja que termine.sí

Ciclo de vida de las tareas asíncronas →

¿Sigues con el problema?

Envíanos el ID de solicitud o el mensaje de error y lo revisaremos.