Riferimento API
Evita addebiti doppi
Idempotenza significa che una richiesta ripetuta viene eseguita una sola volta: invia la stessa Idempotency-Key con il nuovo tentativo e l'addebito avverrà una sola volta, anche se il primo tentativo sembrava solo andato perso.
Una richiesta andata in timeout non è una richiesta fallita. La generazione potrebbe essere già in corso, e un nuovo tentativo alla cieca ne avvia — e ne addebita — una seconda. Invia una Idempotency-Key e il gateway esegue la richiesta una sola volta, poi riproduce la stessa risposta per 24 ore.
#Quando inviare una chiave
Inviala
- Ogni invio asincrono — video, task immagine, musica, 3D. Sono quelli costosi.
- Qualsiasi chiamata non in streaming che il tuo worker riprova automaticamente.
- Chiamate dietro una coda che può riconsegnare un job dopo un crash.
Non serve
- Richieste in streaming — l'header viene rifiutato con 400, uno stream non può essere riprodotto.
- Polling GET e letture di metadati — ripeterli è già gratuito e sicuro.
- Chat interattive in cui una risposta duplicata costa meno della contabilità.
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"
}'Ricava la chiave dal tuo record
#Come vengono confrontate le chiavi
Su attivazione, mai automatico
Senza l'header non cambia nulla. Un nuovo tentativo senza chiave è una seconda richiesta fatturabile.
Solo metodi di scrittura
GET, HEAD e OPTIONS ignorano l'header — interrogare un task è già sicuro da ripetere.
Ambito: utente + chiave + metodo + route
Lo stesso valore con un'altra chiave API, o su un altro endpoint, è un'operazione diversa.
Il corpo fa parte dell'identità
Percorso, query e corpo vengono sottoposti insieme all'hash. Riutilizzare una chiave con un payload modificato restituisce 409, non sovrascrive.
Non compatibile con lo streaming
Una risposta in streaming non può essere salvata per il replay, quindi stream:true insieme a una chiave viene rifiutato con 400.
Blocca in caso di guasto
Se il registro non è disponibile, la richiesta viene rifiutata con 503 invece di essere eseguita senza protezione.
Le chiavi hanno al massimo 255 caratteri. Quelle più lunghe vengono rifiutate con 400 prima che la richiesta venga eseguita.
#Esiti e codici di stato
Una risposta riprodotta include X-Idempotent-Replayed: true e lo stato originale — trattala come la risposta, non come un duplicato da riconciliare. Ogni rifiuto usa la struttura piatta dei controlli preliminari.
| HTTP | Codice | Significato | Cosa fare |
|---|---|---|---|
| 400 | IDEMPOTENCY_STREAM_UNSUPPORTED | Idempotency-Key inviata su una richiesta in streaming. | Rimuovi l'header per le chiamate in streaming, oppure imposta stream:false quando ti serve la protezione da replay. |
| 400 | IDEMPOTENCY_BODY_UNREADABLE | Impossibile calcolare l'hash del corpo per il registro di idempotenza. | Invia un normale corpo JSON o multipart — i corpi chunked che non possono essere bufferizzati vengono rifiutati. |
| 409 | IDEMPOTENCY_PAYLOAD_CONFLICT | Questa chiave è già stata usata con un corpo, un percorso o una query diversi. | Genera una nuova chiave. Una chiave identifica una singola richiesta esatta, non un ciclo di tentativi. |
| 409 | IDEMPOTENCY_IN_PROGRESS | La prima richiesta con questa chiave è ancora in esecuzione. | Attendi Retry-After (2s) e riprova con la stessa chiave — riceverai la risposta originale. |
| 409 | IDEMPOTENCY_RESULT_UNREPLAYABLE | La richiesta originale è terminata, ma la sua risposta è stata sigillata e non può essere riprodotta. | Non reinviare alla cieca. Riconcilia prima tramite l'endpoint del task o i tuoi registri. |
| 503 | IDEMPOTENCY_UNAVAILABLE | Il registro di idempotenza non è disponibile, quindi il controllo blocca la richiesta invece di rischiare un doppio addebito. | Riprova con backoff — la richiesta non è stata eseguita. |
#Ciclo di retry
Una chiave per job, riutilizzata in ogni tentativo, con il 409 in elaborazione gestito come "attendi", non come "errore".
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")Salva l'id del task prima di riprovare qualsiasi cosa
Non hai ancora risolto?
Inviaci l'ID richiesta o il messaggio di errore e verificheremo.