Comment ça marche

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 :

  1. À l'envoi. POST /v1/generations ré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é.
  2. Pendant l'exécution. L'envoi a réussi avec 202, mais la tâche se termine ensuite avec status: "failed". Son error contient un code, un message que vous pouvez montrer à la personne à l'origine de la demande, et retryable.

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

Codes d'erreur HTTP du point de terminaison des générations
StatutCodeSignificationRéessayer ?
400invalid_requestLe 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.
401unauthorizedLa clé API est absente ou incorrecte.Non. Vérifiez la clé.
402insufficient_creditsLe solde ne couvre pas le prix bloqué pour la tâche.Après une recharge.
403account_disabled, generation_not_enabledLe compte est suspendu, ou la clé peut consulter les prix mais pas lancer de génération.Non. Contactez le support.
404not_foundAucune génération avec cet identifiant sur votre compte.Non.
409idempotency_conflictLa Idempotency-Key a déjà été utilisée avec un corps différent.Avec une nouvelle clé.
409not_readyVous avez demandé le fichier avant la réussite de la tâche.Oui, une fois qu'elle a réussi.
413body_too_largeLe corps JSON dépasse 1 Mo. Téléversez les fichiers et envoyez des liens à la place.Non. Réduisez le corps.
422invalid_inputUne 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.
422max_cost_exceededToutes les routes coûtent plus que votre max_cost_usd.Non. Relevez le plafond ou réduisez les réglages.
422model_not_availableAucun modèle avec cet identifiant.Non. Vérifiez l'identifiant.
429rate_limitedTrop de requêtes dans la même seconde depuis un seul compte.Oui, après Retry-After.
429concurrency_limitLe compte a déjà atteint son nombre maximal de tâches en cours.Oui, quand l'une se termine.
500server_errorUn problème est survenu de notre côté.Oui, avec la même Idempotency-Key.
503busyUn pic de charge passager.Oui, après Retry-After, avec la même clé.
503model_unavailableAucune 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é

Codes d'erreur d'une génération en échec
error.coderetryableCe qui s'est passé
content_policynoLe 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_inputnoUne référence n'a pas pu être lue, ou le modèle a rejeté une valeur. Le message indique laquelle.
model_declinednoLe 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_changednoUn fichier de référence a changé après sa vérification. Envoyez-le à nouveau.
max_cost_exceedednoLa seule route capable d'exécuter la tâche coûte plus que votre plafond.
model_busyyesToutes les routes étaient saturées. Réessayez dans une minute.
model_unavailableyesAucune route ne pouvait prendre la tâche en charge pour le moment.
timeoutyesAucune route n'a renvoyé de résultat à temps, ou votre max_wait_s est dépassé.
all_routes_failedyesToutes 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 503 ou une connexion coupée utilise la même Idempotency-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.
Python
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/models et 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 avec 422 invalid_input.
  • Téléversez les fichiers au lieu de les intégrer. POST /v1/uploads accepte 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/balance avant un gros lot vous évite une série d'erreurs 402. 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.