Códigos de error de la API de vídeo con IA: qué significan y cuándo reintentar
Los trabajos de generación de vídeo fallan más a menudo que las llamadas a una API normal: los modelos se saturan, rechazan prompts o agotan el tiempo de espera. Esta guía enumera todos los errores que puedes recibir, cuáles vale la pena reintentar y cómo hacerlo de forma segura.
Dos lugares donde puede aparecer un error
Un trabajo de vídeo o imagen puede fallar en dos momentos, y tu código tiene que gestionar ambos:
- Al enviarlo.
POST /v1/generationsresponde con un estado de error HTTP y un cuerpo como{"error": {"code": "...", "message": "..."}}. No se inició nada y no se cobra nada. - Mientras se ejecuta. El envío funcionó y devolvió
202, pero el trabajo termina después constatus: "failed". Suerrorincluye uncode, unmessageque puedes mostrar a quien lo pidió, yretryable.
Un trabajo fallido nunca se cobra, con ningún modelo. El precio se retiene de tu saldo al enviar y se libera si el trabajo falla.
Errores HTTP al enviar
| Estado | Código | Significado | ¿Reintentar? |
|---|---|---|---|
| 400 | invalid_request | El cuerpo no es JSON, le falta model o input, o contiene un campo que la API no conoce. | No. Corrige el cuerpo. |
| 401 | unauthorized | Falta la clave de API o es incorrecta. | No. Revisa la clave. |
| 402 | insufficient_credits | El saldo no cubre el precio retenido para el trabajo. | Después de recargar. |
| 403 | account_disabled, generation_not_enabled | La cuenta está en pausa, o la clave puede consultar precios pero no generar. | No. Contacta con soporte. |
| 404 | not_found | No hay ninguna generación con ese id en tu cuenta. | No. |
| 409 | idempotency_conflict | La Idempotency-Key ya se usó con un cuerpo distinto. | Con una clave nueva. |
| 409 | not_ready | Pediste el archivo antes de que el trabajo terminara con éxito. | Sí, una vez que haya terminado con éxito. |
| 413 | body_too_large | El cuerpo JSON supera 1 MB. Sube los archivos y envía enlaces. | No. Reduce el cuerpo. |
| 422 | invalid_input | Un valor que el modelo no admite, como una duración o un tamaño que no figura en su lista. El mensaje indica cuál. | No. Cambia el valor. |
| 422 | max_cost_exceeded | Todas las rutas cuestan más que tu max_cost_usd. | No. Sube el límite o reduce los ajustes. |
| 422 | model_not_available | No existe ningún modelo con ese id. | No. Revisa el id. |
| 429 | rate_limited | Demasiadas solicitudes en el mismo segundo desde una cuenta. | Sí, después de Retry-After. |
| 429 | concurrency_limit | La cuenta ya tiene el máximo de trabajos en ejecución. | Sí, cuando termine alguno. |
| 500 | server_error | Algo falló por nuestra parte. | Sí, con la misma Idempotency-Key. |
| 503 | busy | Un momento de carga alta. | Sí, después de Retry-After, con la misma clave. |
| 503 | model_unavailable | Ahora mismo no hay ninguna ruta disponible para este modelo. | Sí, en breve. |
La regla general: vale la pena reintentar 429, 500 y 503 con espera progresiva; cualquier error del rango 4xx salvo 429 significa que hay que cambiar la solicitud en sí.
Por qué falla un trabajo después de empezar
| error.code | retryable | Qué pasó |
|---|---|---|
content_policy | no | El modelo rechazó el prompt o los archivos, o retuvo su resultado. reason indica el motivo: sexual, minors, copyright, real_person o other. |
invalid_input | no | No se pudo leer una referencia, o el modelo rechazó un valor. El mensaje indica cuál. |
model_declined | no | El modelo respondió con texto en lugar de crear la imagen o el vídeo. Describe el resultado en sí en el prompt. |
reference_changed | no | Un archivo de referencia cambió después de verificarse. Envíalo de nuevo. |
max_cost_exceeded | no | La única ruta que puede ejecutarlo cuesta más que tu límite. |
model_busy | yes | Todas las rutas estaban al límite de capacidad. Inténtalo de nuevo en un minuto. |
model_unavailable | yes | Ninguna ruta pudo aceptar el trabajo en este momento. |
timeout | yes | Ninguna ruta devolvió un resultado a tiempo, o se superó tu max_wait_s. |
all_routes_failed | yes | Todas las rutas fallaron por motivos de su lado. |
Entre bambalinas, cuando un trabajo llega a una ruta saturada o averiada, pasa por sí solo a la siguiente, así que cuando ves model_busy o all_routes_failed ya se probaron todas las rutas. El campo attempts indica cuántas. Esperar uno o dos minutos antes de volver a intentarlo suele bastar.
Rechazos por contenido
content_policy es el fallo más frecuente. Lo decide el modelo, no la API, y el reason indica qué regla se activó. Las causas habituales son el nombre o el rostro de una persona famosa en el prompt o en una imagen de referencia (real_person), marcas y personajes conocidos (copyright) y cualquier cosa que parezca implicar a un menor. Reintentar la misma solicitud no sirve: reformula el prompt o cambia la imagen. Los modelos difieren en su nivel de rigor, así que el mismo prompt puede pasar en uno y ser rechazado en otro.
Un bucle de reintentos que hace lo correcto
Este helper de Python reintenta lo que vale la pena y se detiene ante lo que no. Importan dos detalles:
- Reenviar un envío tras un
503o una conexión caída usa la mismaIdempotency-Key, así que nunca puede iniciar un segundo trabajo de pago. - Volver a intentarlo cuando un trabajo falló usa una clave nueva. La misma clave solo devolvería el trabajo fallido.
import os
import time
import uuid
import requests
API = "https://api.revana.si/v1"
AUTH = {"Authorization": f"Bearer {os.environ['REVANA_API_KEY']}"}
RETRY_HTTP = {429, 500, 503}
class GenerationError(Exception):
pass
def submit(body):
"""POST a job. Retries 429, 500 and 503 with the same Idempotency-Key, so a retry never starts a second job."""
headers = {**AUTH, "Idempotency-Key": str(uuid.uuid4())}
for attempt in range(6):
r = requests.post(f"{API}/generations", headers=headers, json=body, timeout=60)
if r.ok:
return r.json()
if r.status_code in RETRY_HTTP:
time.sleep(float(r.headers.get("Retry-After", 2 ** attempt)))
continue
err = r.json()["error"] # 400, 401, 402, 403, 409, 413, 422: fix the request, do not retry
raise GenerationError(f"{r.status_code} {err['code']}: {err['message']}")
raise GenerationError("still busy after 6 tries")
def wait(job):
while job["status"] in ("queued", "running"):
time.sleep(5)
job = requests.get(f"{API}/generations/{job['id']}", headers=AUTH).json()
return job
def generate(body, tries=3):
for n in range(tries):
job = wait(submit(body)) # each call uses a new key: a failed job is final
if job["status"] == "succeeded":
return job
err = job["error"]
if not err["retryable"]:
raise GenerationError(f"{err['code']}: {err['message']}")
time.sleep(60 * (n + 1)) # model_busy, timeout and the like: wait, then try again
raise GenerationError(f"gave up after {tries} tries: {err['code']}")
job = generate({
"model": "seedance-2.0-fast",
"input": {"prompt": "Waves roll onto black sand at sunset.", "resolution": "480p", "duration": 5},
"max_cost_usd": 0.3,
})
print(job["output"]["urls"][0], job["cost_usd"])El trabajo de ejemplo es un clip de Seedance 2.0 Fast de 5 segundos en 480p, que cuesta $0.25 si termina bien y nada si falla. El límite max_cost_usd evita que un ajuste equivocado cueste más de lo esperado.
Cómo evitar errores desde el principio
- Lee las opciones antes de enviar.
GET /v1/modelsy la página de modelos enumeran los tamaños, las duraciones y los límites de referencias de cada modelo. Un valor fuera de ellos se rechaza al instante con422 invalid_input. - Sube los archivos en lugar de incrustarlos.
POST /v1/uploadsacepta imágenes de hasta 30 MB, clips de hasta 50 MB y audio de hasta 15 MB, y devuelve un enlace válido durante 14 días, lo que mantiene pequeño el cuerpo de la solicitud. - Comprueba tu saldo desde el código.
GET /v1/balanceantes de un lote grande te ahorra una racha de errores402. Los paquetes de créditos están en la página de precios.
Para reducir lo que cuestan los trabajos que sí terminan, consulta cómo reducir los costes de la API de vídeo con IA. La referencia completa de errores está en la documentación; obtén una clave de API para probarlo.
Preguntas frecuentes
¿Se me cobra si falla una generación de vídeo con IA?
No. El precio se retiene al enviar el trabajo y se libera si este falla, sea cual sea el motivo.
¿Qué errores debo reintentar?
Al enviar, reintenta 429, 500 y 503 después del tiempo indicado en Retry-After. En un trabajo fallido, reintenta cuando error.retryable sea true: model_busy, model_unavailable, timeout y all_routes_failed.
¿Qué significa content_policy?
El modelo rechazó el prompt o los archivos, o retuvo lo que generó. El campo reason indica la regla: sexual, minors, copyright, real_person u other. Cambia la solicitud en lugar de reintentarla.
¿Cómo reintento sin pagar dos veces?
Envía un encabezado Idempotency-Key y reutilízalo cuando vuelvas a enviar tras un timeout o un 503. Recibirás la misma generación, no una segunda.
¿Qué significa 422 invalid_input?
El modelo no admite un valor que enviaste, como una duración, un tamaño o un número de imágenes de referencia. El mensaje indica el campo y lo que admite.
Crea sobre una API que nunca cobra un fallo
Una clave de API para todos los modelos de vídeo e imagen, con respaldo automático entre rutas. Crédito prepago desde $10, cobrado solo por los trabajos que terminan con éxito.