Référence API

Éviter les doubles débits

L'idempotence garantit qu'une requête réessayée n'est exécutée qu'une fois : envoyez la même Idempotency-Key avec la nouvelle tentative et vous n'êtes débité qu'une seule fois, même si la première tentative semblait seulement perdue.

Une requête expirée n'est pas une requête échouée. La génération est peut-être déjà en cours, et une nouvelle tentative à l'aveugle en lance — et en facture — une seconde. Envoyez une Idempotency-Key : la passerelle exécute la requête une seule fois, puis rejoue la même réponse pendant 24 heures.

#Quand envoyer une clé

À envoyer

  • Toute soumission asynchrone — vidéo, tâche d'image, musique, 3D. Ce sont les plus coûteuses.
  • Tout appel sans streaming que votre worker réessaie automatiquement.
  • Les appels derrière une file qui peut redistribuer un traitement après un plantage.

Inutile

  • Requêtes en streaming — l'en-tête est rejeté avec 400, un stream ne peut pas être rejoué.
  • Interrogations GET et lectures de métadonnées — les répéter est déjà gratuit et sans risque.
  • Chat interactif, où une réponse en double coûte moins cher que la gestion des clés.
submit.sh
curl https://www.aisplice.store/v1/videos \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "model": "doubao-seedance-2.5",
    "prompt": "A drone shot of a lighthouse at dawn"
  }'

Dérivez la clé de votre propre enregistrement

Un UUID généré dans la boucle de tentatives va à l'encontre du but — chaque tentative reçoit une nouvelle identité. Dérivez-la de la ligne métier que vous traitez (id de commande, id de traitement) pour que chaque nouvelle tentative de ce traitement porte la même clé.

#Comment les clés sont comparées

À activer explicitement, jamais automatique

Sans l'en-tête, rien ne change. Une nouvelle tentative sans clé est une seconde requête facturable.

Méthodes d'écriture uniquement

GET, HEAD et OPTIONS ignorent l'en-tête — interroger une tâche peut déjà être répété sans risque.

Portée : utilisateur + clé + méthode + route

La même valeur avec une autre clé API, ou sur un autre endpoint, correspond à une opération différente.

Le corps fait partie de l'identité

Le chemin, les paramètres de requête et le corps sont hachés ensemble. Réutiliser une clé avec une charge utile modifiée renvoie 409 ; rien n'est écrasé.

Incompatible avec le streaming

Une réponse en streaming ne peut pas être stockée pour être rejouée : stream:true avec une clé est rejeté avec 400.

Refus en cas de panne

Si le registre est indisponible, la requête est refusée avec 503 plutôt qu'exécutée sans protection.

Les clés font au plus 255 caractères. Toute clé plus longue est rejetée avec 400 avant l'exécution de la requête.

#Résultats et codes de statut

Une réponse rejouée porte X-Idempotent-Replayed: true et le statut d'origine — traitez-la comme la réponse, pas comme un doublon à rapprocher. Chaque rejet utilise l'enveloppe plate des garde-fous.

HTTPCodeSignificationQue faire
400IDEMPOTENCY_STREAM_UNSUPPORTEDUne Idempotency-Key a été envoyée sur une requête en streaming.Retirez l'en-tête pour les appels en streaming, ou définissez stream:false si vous avez besoin de la protection par rejeu.
400IDEMPOTENCY_BODY_UNREADABLELe corps n'a pas pu être haché pour le registre d'idempotence.Envoyez un corps JSON ou multipart classique — les corps chunked qui ne peuvent pas être mis en mémoire tampon sont rejetés.
409IDEMPOTENCY_PAYLOAD_CONFLICTCette clé a déjà été utilisée avec un corps, un chemin ou des paramètres de requête différents.Générez une nouvelle clé. Une clé identifie une requête précise, pas une boucle de tentatives.
409IDEMPOTENCY_IN_PROGRESSLa première requête avec cette clé est toujours en cours.Attendez Retry-After (2 s) et réessayez avec la même clé — vous obtiendrez la réponse d'origine.
409IDEMPOTENCY_RESULT_UNREPLAYABLELa requête d'origine est terminée, mais sa réponse a été scellée et ne peut pas être rejouée.Ne renvoyez pas la requête à l'aveugle. Faites d'abord le rapprochement via l'endpoint de la tâche ou vos propres enregistrements.
503IDEMPOTENCY_UNAVAILABLELe registre d'idempotence est indisponible : le garde-fou refuse la requête plutôt que de risquer un double débit.Réessayez avec backoff — la requête n'a pas été exécutée.

#Boucle de tentatives

Une clé par traitement, réutilisée à chaque tentative, avec le 409 « en cours » traité comme « attendre » et non comme « échec ».

submit_once.py
import time, uuid, requests

def submit_once(job_id: str, body: dict, api_key: str, tries: int = 5):
    # Same key for every attempt of this job — that is what makes it safe.
    key = str(uuid.uuid5(uuid.NAMESPACE_URL, f"video-job:{job_id}"))

    for attempt in range(tries):
        response = requests.post(
            f"https://www.aisplice.store/v1/videos",
            headers={
                "Authorization": f"Bearer {api_key}",
                "Idempotency-Key": key,
            },
            json=body,
            timeout=60,
        )

        if response.status_code == 409:
            code = response.json().get("code", "")
            if code == "IDEMPOTENCY_IN_PROGRESS":
                time.sleep(int(response.headers.get("Retry-After", 2)))
                continue
            # PAYLOAD_CONFLICT or RESULT_UNREPLAYABLE: a human decision,
            # never a silent resubmit.
            raise RuntimeError(response.text)

        if response.status_code >= 500:
            time.sleep(2 ** attempt)
            continue

        response.raise_for_status()
        replayed = response.headers.get("X-Idempotent-Replayed") == "true"
        return response.json(), replayed

    raise TimeoutError(f"submit for {job_id} did not settle")

Enregistrez l'id de la tâche avant toute nouvelle tentative

Écrivez task_id dans votre base de données, dans la même transaction que celle qui marque le traitement comme soumis. La clé protège la soumission ; seul votre propre enregistrement vous protège une fois la réponse perdue.

Cycle de vie d'une tâche asynchrone →

Toujours bloqué ?

Envoyez-nous l'ID de requête ou le message d'erreur, et nous examinerons le problème.