Errores
Cada código de negocio, qué significa y qué errores vale la pena reintentar.
Cuando una llamada falla, la respuesta te da un número (code) que dice qué tipo de problema es y una frase (msg) para que la lea una persona. Decide según el número; muestra o registra la frase.
El sobre
{ "code": 40201, "msg": "Insufficient balance", "request_id": "req_…" }codees un código de negocio.200significa éxito; todo lo demás está abajo.- Si falla no hay campo
data, salvo en algunos errores de la biblioteca de recursos, que añadendata.reason. - El estado HTTP también se establece (
40201llega con HTTP402). Decide con uno de los dos, no con una mezcla; te recomendamoscode, que es más concreto. request_idtambién va en la cabeceraX-Request-Id. Inclúyelo cuando escribas a soporte.
Si una respuesta ni siquiera es JSON —una página HTML o de texto de la red intermedia—, trátala según el estado HTTP y registra el principio del cuerpo.
Idioma de los mensajes de error
msg y el errorMessage de una tarea fallida vienen en inglés por defecto. Envía Accept-Language con en, ja, ko, de, fr, es, pt-BR, ru, it o pl, o configura el idioma de errores de la API en la consola, para recibirlos en ese idioma; el resto vuelve al inglés. Las respuestas de error llevan Content-Language. Los códigos nunca cambian con el idioma: otra razón para no analizar nunca msg.
Cuando un modelo da un motivo concreto de fallo (por ejemplo, una imagen de referencia ilegible), errorMessage lo transmite en inglés, sin detalles internos.
Códigos generales
code | HTTP | Significado | ¿Reintentar? |
|---|---|---|---|
200 | 200 / 202 | Éxito | — |
400 | 400 | La solicitud o su input no son válidos; msg dice qué falla | No: corrígelo |
40003 | 400 | Una subida no coincide con su ticket (tamaño, tipo o contenido) | No: pide otro ticket y súbelo de nuevo |
40004 | 400 | Solicitud válida, pero el modelo no puede atender ahora esta combinación de ajustes | No: cambia el ajuste que nombra msg |
401 | 401 | Falta la clave, es desconocida o está revocada (detalles) | No |
403 | 403 | No permitido (mira Permisos) | No |
404 | 404 | No existe o pertenece a otra clave | No |
409 | 409 | Choca con el estado actual (cuándo reintentar) | A veces |
40901 | 409 | Una cotización caducó o el precio cambió | Vuelve a cotizar |
413 | 413 | Cuerpo de la solicitud demasiado grande | No: sube los archivos en vez de incrustarlos |
429 | 429 | Demasiadas solicitudes | Sí, tras Retry-After |
500 | 500 | Algo falló de nuestro lado | Sí, con espera creciente |
503 | 503 | Un servicio de apoyo no está disponible un momento | Sí, tras Retry-After |
Saldo y topes
code | HTTP | Significado | Qué hacer |
|---|---|---|---|
40201 | 402 | No hay créditos suficientes para la retención de esta tarea | Compra créditos y vuelve a enviarla |
40202 | 402 | Se alcanzó un tope de gasto de la clave | Súbelo en la consola de la API o espera a que se reinicie |
Permisos
code | HTTP | Significado | Qué hacer |
|---|---|---|---|
40301 | 403 | Esta clave no puede llamar a este modelo | Añade el modelo a la lista permitida de la clave |
40302 | 403 | La solicitud vino de una dirección fuera de la lista de IP permitidas | Añade la dirección o quita la restricción |
40303 | 403 | El servicio no se ofrece en esta región | No se puede sortear |
40310 | 403 | El correo de la cuenta aún no está verificado | Abre el enlace del correo de registro; la clave funciona en cuanto se verifica |
Un 403 sin más con una clave válida significa que la cuenta no puede usar la API ahora mismo; contacta con soporte con el request_id.
Disponibilidad de modelos
code | HTTP | Significado | ¿Reintentar? |
|---|---|---|---|
50301 | 503 | El modelo no puede aceptar tareas ahora | Sí, más tarde, o elige otro modelo |
40004 | 400 | El modelo funciona, pero no con estos ajustes | No: cambia el ajuste |
Ninguno de los dos crea una tarea ni retiene créditos.
Códigos de fallo de tareas
Una tarea aceptada que luego falla tiene state: "failed" y uno de estos valores en errorCode. La lista es cerrada: trata cualquier otro valor como upstream_failed.
errorCode | Significado | ¿Reintentar? |
|---|---|---|
invalid_request | El modelo no aceptó los parámetros | No: corrígelos |
unsupported_combination | Esta combinación de ajustes no se admite ahora | No: cambia un ajuste |
content_rejected | El modelo rechazó este contenido | No: cambia el prompt o la referencia |
invalid_asset | Un archivo de referencia no se pudo leer o ya no está disponible | Súbelo de nuevo y crea otra tarea |
rate_limited | El servicio de generación estaba saturado | Sí, con espera creciente |
upstream_unavailable | La generación no está disponible por un momento | Sí, en breve |
generation_failed | La generación se ejecutó pero no produjo nada | Puede ser: la misma entrada suele fallar igual |
timeout | La tarea se quedó sin tiempo | Puede ser: prueba una salida más corta o con menos resolución |
upstream_failed | Todo lo demás | Sí, con espera creciente |
Una tarea fallida libera su retención, así que no cuesta nada (unos pocos modelos cobran los rechazos por contenido y lo indican en su página). errorMessage explica el motivo con palabras; decide solo según errorCode.
Cuándo vale la pena reintentar un 409
| Caso | ¿Reintentar? |
|---|---|
Llegaron a la vez dos solicitudes con el mismo Idempotency-Key | Sí, con la misma clave, tras una pausa corta |
El Idempotency-Key se usó para otra solicitud o con otra clave | No |
| Una subida o un resultado aún se está procesando | Sí, al cabo de unos segundos |
jobs/retry sobre una tarea que todavía no terminó | Sí, cuando sea final |
jobs/retry sobre una tarea que salió bien | No: crea una tarea nueva |
| Caducó un ticket de subida o un archivo subido | No: súbelo otra vez |
Cómo escribir el reintento
Reintenta solo lo que puede salir bien a la segunda, espera Retry-After cuando venga y, si no, aumenta la espera de forma exponencial:
const RETRYABLE = new Set([429, 500, 503, 50301]);
async function call(path, init, attempts = 4) {
for (let i = 0; i < attempts; i++) {
const res = await fetch(`https://api.popnow.ai/api/v1${path}`, init);
if (!res.headers.get('content-type')?.includes('json')) {
throw new Error(`HTTP ${res.status}: ${(await res.text()).slice(0, 200)}`);
}
const body = await res.json();
if (body.code === 200) return body.data;
if (!RETRYABLE.has(body.code)) throw new Error(`${body.code} ${body.msg} (${body.request_id})`);
const wait = Number(res.headers.get('retry-after')) * 1000 || 2 ** i * 1000 + Math.random() * 500;
await new Promise((r) => setTimeout(r, wait));
}
throw new Error('out of retries');
}import random, time, requests
RETRYABLE = {429, 500, 503, 50301}
def call(method, path, attempts=4, **kwargs):
for i in range(attempts):
res = requests.request(method, f"https://api.popnow.ai/api/v1{path}", timeout=60, **kwargs)
try:
body = res.json()
except ValueError:
raise RuntimeError(f"HTTP {res.status_code}: {res.text[:200]}")
if body["code"] == 200:
return body["data"]
if body["code"] not in RETRYABLE:
raise RuntimeError(f'{body["code"]} {body["msg"]} ({body.get("request_id")})')
retry_after = res.headers.get("retry-after")
time.sleep(float(retry_after) if retry_after else 2**i + random.random() / 2)
raise RuntimeError("out of retries")Al reintentar createTask, envía siempre el mismo Idempotency-Key, para que una solicitud que en realidad funcionó no se ejecute ni se cobre dos veces.
Depuración
- Apunta el
request_idy elcode. - Busca la tarea en los logs de la consola de la API, por ID de tarea o
request_id. - ¿Sigues atascado? Contacta con soporte con el
request_id, la hora (UTC) y elcode. Nunca envíes tu clave de API.

