Codes d'erreur de l'API vidéo IA : ce qu'ils signifient et quand réessayer
Les tâches de génération vidéo échouent plus souvent que les appels d'API ordinaires : les modèles sont saturés, refusent des prompts ou dépassent le délai. Ce guide recense toutes les erreurs que vous pouvez recevoir, celles qui valent la peine d'être réessayées et la façon de réessayer sans risque.
Deux endroits où une erreur peut apparaître
Une tâche vidéo ou image peut échouer à deux moments, et votre code doit gérer les deux :
- À l'envoi.
POST /v1/generationsrépond avec un statut d'erreur HTTP et un corps du type{"error": {"code": "...", "message": "..."}}. Rien n'a démarré et rien n'est facturé. - Pendant l'exécution. L'envoi a réussi avec
202, mais la tâche se termine ensuite avecstatus: "failed". Sonerrorcontient uncode, unmessageque vous pouvez montrer à la personne à l'origine de la demande, etretryable.
Une tâche en échec n'est jamais facturée, quel que soit le modèle. Le prix est bloqué sur votre solde à l'envoi et libéré si la tâche échoue.
Erreurs HTTP à l'envoi
| Statut | Code | Signification | Réessayer ? |
|---|---|---|---|
| 400 | invalid_request | Le corps n'est pas du JSON, il n'a pas model ou input, ou il contient un champ inconnu de l'API. | Non. Corrigez le corps. |
| 401 | unauthorized | La clé API est absente ou incorrecte. | Non. Vérifiez la clé. |
| 402 | insufficient_credits | Le solde ne couvre pas le prix bloqué pour la tâche. | Après une recharge. |
| 403 | account_disabled, generation_not_enabled | Le compte est suspendu, ou la clé peut consulter les prix mais pas lancer de génération. | Non. Contactez le support. |
| 404 | not_found | Aucune génération avec cet identifiant sur votre compte. | Non. |
| 409 | idempotency_conflict | La Idempotency-Key a déjà été utilisée avec un corps différent. | Avec une nouvelle clé. |
| 409 | not_ready | Vous avez demandé le fichier avant la réussite de la tâche. | Oui, une fois qu'elle a réussi. |
| 413 | body_too_large | Le corps JSON dépasse 1 Mo. Téléversez les fichiers et envoyez des liens à la place. | Non. Réduisez le corps. |
| 422 | invalid_input | Une valeur que le modèle n'accepte pas, par exemple une durée ou une taille qu'il ne propose pas. Le message indique laquelle. | Non. Changez la valeur. |
| 422 | max_cost_exceeded | Toutes les routes coûtent plus que votre max_cost_usd. | Non. Relevez le plafond ou réduisez les réglages. |
| 422 | model_not_available | Aucun modèle avec cet identifiant. | Non. Vérifiez l'identifiant. |
| 429 | rate_limited | Trop de requêtes dans la même seconde depuis un seul compte. | Oui, après Retry-After. |
| 429 | concurrency_limit | Le compte a déjà atteint son nombre maximal de tâches en cours. | Oui, quand l'une se termine. |
| 500 | server_error | Un problème est survenu de notre côté. | Oui, avec la même Idempotency-Key. |
| 503 | busy | Un pic de charge passager. | Oui, après Retry-After, avec la même clé. |
| 503 | model_unavailable | Aucune route n'est disponible pour ce modèle pour le moment. | Oui, sous peu. |
La règle générale : 429, 500 et 503 valent la peine d'être réessayées avec un délai d'attente croissant ; toute erreur de la plage 4xx autre que 429 signifie que la requête elle-même doit changer.
Pourquoi une tâche échoue après avoir démarré
| error.code | retryable | Ce qui s'est passé |
|---|---|---|
content_policy | no | Le modèle a refusé le prompt ou le média, ou a retenu son résultat. reason en donne la raison : sexual, minors, copyright, real_person ou other. |
invalid_input | no | Une référence n'a pas pu être lue, ou le modèle a rejeté une valeur. Le message indique laquelle. |
model_declined | no | Le modèle a répondu par du texte au lieu de produire l'image ou la vidéo. Décrivez le résultat lui-même dans le prompt. |
reference_changed | no | Un fichier de référence a changé après sa vérification. Envoyez-le à nouveau. |
max_cost_exceeded | no | La seule route capable d'exécuter la tâche coûte plus que votre plafond. |
model_busy | yes | Toutes les routes étaient saturées. Réessayez dans une minute. |
model_unavailable | yes | Aucune route ne pouvait prendre la tâche en charge pour le moment. |
timeout | yes | Aucune route n'a renvoyé de résultat à temps, ou votre max_wait_s est dépassé. |
all_routes_failed | yes | Toutes les routes ont échoué pour des raisons qui leur sont propres. |
En coulisses, une tâche qui tombe sur une route saturée ou défaillante passe d'elle-même à la route suivante : lorsque vous voyez model_busy ou all_routes_failed, toutes les routes ont donc déjà été essayées. Le champ attempts indique combien. Attendre une minute ou deux avant de réessayer suffit généralement.
Refus de contenu
content_policy est l'échec que l'on rencontre le plus. C'est le modèle, et non l'API, qui en décide, et reason indique quelle règle s'est déclenchée. Les causes courantes sont le nom ou le visage d'une personne célèbre dans le prompt ou dans une image de référence (real_person), les marques et les personnages connus (copyright), et tout ce qui laisse penser qu'un mineur est concerné. Réessayer la même requête n'y changera rien ; reformulez le prompt ou changez l'image. Les modèles n'ont pas tous la même sévérité : un même prompt peut passer sur l'un et être refusé sur un autre.
Une boucle de nouvelles tentatives qui fait ce qu'il faut
Cette fonction Python réessaie ce qui mérite de l'être et s'arrête sur le reste. Deux détails comptent :
- Renvoyer une soumission après une erreur
503ou une connexion coupée utilise la mêmeIdempotency-Key, de sorte qu'elle ne peut jamais lancer une seconde tâche payante. - Réessayer après l'échec d'une tâche utilise une nouvelle clé. La même clé ne ferait que renvoyer la tâche en échec.
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"])La tâche d'exemple est un clip Seedance 2.0 Fast de 5 secondes en 480p, qui coûte $0.25 s'il réussit et rien s'il échoue. Le plafond max_cost_usd empêche un mauvais réglage de coûter plus que prévu.
Éviter les erreurs dès le départ
- Lisez les options avant d'envoyer.
GET /v1/modelset la page des modèles listent les tailles, les durées et les limites de références de chaque modèle. Une valeur hors de ces limites est refusée immédiatement avec422 invalid_input. - Téléversez les fichiers au lieu de les intégrer.
POST /v1/uploadsaccepte des images jusqu'à 30 Mo, des clips jusqu'à 50 Mo et du son jusqu'à 15 Mo, et renvoie un lien valable 14 jours, ce qui garde le corps de la requête léger. - Vérifiez votre solde dans le code.
GET /v1/balanceavant un gros lot vous évite une série d'erreurs402. Les packs de crédits sont sur la page des tarifs.
Pour réduire le coût des tâches réussies, voir comment réduire le coût de l'API vidéo IA. La référence complète des erreurs se trouve dans la documentation ; obtenez une clé API pour essayer.
Questions fréquentes
Suis-je facturé quand une génération vidéo échoue ?
Non. Le prix est bloqué à l'envoi et libéré si la tâche échoue, quelle qu'en soit la raison.
Quelles erreurs faut-il réessayer ?
À l'envoi, réessayez les erreurs 429, 500 et 503 après le délai indiqué par Retry-After. Pour une tâche en échec, réessayez lorsque error.retryable vaut true : model_busy, model_unavailable, timeout et all_routes_failed.
Que signifie content_policy ?
Le modèle a refusé le prompt ou le média, ou a retenu ce qu'il avait produit. Le champ reason indique la règle concernée : sexual, minors, copyright, real_person ou other. Modifiez la requête au lieu de la renvoyer telle quelle.
Comment réessayer sans payer deux fois ?
Envoyez un en-tête Idempotency-Key et réutilisez-le quand vous renvoyez la requête après un délai dépassé ou une erreur 503. Vous récupérez la même génération, pas une seconde.
Que signifie l'erreur 422 invalid_input ?
Le modèle n'accepte pas une valeur que vous avez envoyée, par exemple une durée, une taille ou un nombre d'images de référence. Le message indique le champ concerné et ce qu'il accepte.
Construisez sur une API qui ne facture jamais un échec
Une seule clé API pour tous les modèles vidéo et image, avec repli automatique entre les routes. Crédit prépayé à partir de $10, facturé uniquement pour les tâches réussies.