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
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
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
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
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.
{
"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.
{
"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.
{
"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.
{
"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/1.1 429 Too Many Requests
Retry-After: 20
Content-Length: 0Encabezados 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.
| Encabezado | Cuándo se envía | Qué hacer con él |
|---|---|---|
| X-Oneapi-Request-Id | Cada 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-After | En 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-Replayed | Se 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-Sealed | La 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. |
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.
| HTTP | Código | Significado | Qué hacer | ¿Reintentar? |
|---|---|---|---|---|
| 401 | ninguno | Bearer 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 |
| 401 | signature_required | La 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 |
| 401 | signature_expired | X-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í |
| 401 | signature_invalid | X-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 |
| 401 | signature_replayed | Este 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í |
| 403 | access_denied | La 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 |
| 403 | ninguno | Cuenta 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 |
| 400 | unsupported_endpoint | El 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 |
| 400 | read_request_body_failed | No 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 |
| 402 | insufficient_user_quota | El saldo de la billetera no cubre el importe reservado previamente para esta llamada. | Recarga en Consola → Billetera y vuelve a intentarlo. | no |
| 429 | insufficient_user_quota | Se 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 |
| 429 | ninguno | Limitació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í |
| 413 | read_request_body_failed | El 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 |
| 422 | invalid_request | Ningú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 |
| 503 | model_not_found | Nombre 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í |
| 500 | sensitive_words_detected | El prompt activó el filtro de contenido antes de salir del gateway. | Cambia el prompt. Reintentar el mismo texto siempre falla. | no |
| 500 | count_token_failed | El 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í |
| 500 | invalid_request | No 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 |
| 502 | do_request_failed | No 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í |
| 504 | do_request_failed | El 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í |
| 499 | do_request_failed | Tu 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.
| HTTP | Código | Significado | Qué hacer | ¿Reintentar? |
|---|---|---|---|---|
| 400 | IDEMPOTENCY_STREAM_UNSUPPORTED | Se 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 |
| 400 | IDEMPOTENCY_BODY_UNREADABLE | No 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 |
| 409 | IDEMPOTENCY_PAYLOAD_CONFLICT | Esta 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 |
| 409 | IDEMPOTENCY_IN_PROGRESS | La 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í |
| 409 | IDEMPOTENCY_RESULT_UNREPLAYABLE | La 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 |
| 503 | IDEMPOTENCY_UNAVAILABLE | El 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í |
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.
| HTTP | Código | Significado | Qué hacer | ¿Reintentar? |
|---|---|---|---|---|
| 400 | task_not_exist | Consultar 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 |
| 400 | invalid_request | Falta un campo obligatorio — prompt en un remix, task_id en una consulta. | Corrige el cuerpo o la ruta antes de reintentar. | no |
| 400 | task_channel_disable | Una 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 |
| 400 | invalid_source_task | La 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 |
| 404 | task_not_exist | Cancelar un id de tarea que no existe o que no es tuyo. | No hay nada que cancelar — trátala como ya eliminada. | no |
| 409 | task_not_cancellable | La 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 |
| 501 | task_cancel_not_supported | El 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 |
| 502 | cancel_task_failed | No se pudo contactar con el proveedor al cancelar. La tarea no cambia. | Reintenta una vez y, si falla, deja que termine. | sí |
¿Sigues con el problema?
Envíanos el ID de solicitud o el mensaje de error y lo revisaremos.