Idempotenz
Nach einem Timeout sicher wiederholen, ohne doppelt zu generieren oder zu bezahlen.
Ein Idempotenzschlüssel kennzeichnet eine einzelne Arbeit, etwa „Video für Bestellung 8814 rendern“. Erreicht uns dieselbe gekennzeichnete Anfrage zweimal, führen wir sie einmal aus.
createTask akzeptiert ihn im Idempotency-Key-Header. Zweimaliges Senden mit demselben Schlüssel vom selben Konto und demselben API-Key liefert den beim ersten Versuch erstellten Task: kein zweiter Warteschlangeneintrag, keine zweite Reservierung oder Belastung.
POST /api/v1/jobs/createTask
Idempotency-Key: order-8814-render-1Der Schlüssel ist an Ihre gesendete Eingabe gebunden: Modell, gesamter input und callBackUrl. Feldreihenfolge und Leerzeichen sind egal, tatsächliche Änderungen nicht. Derselbe Schlüssel mit anderer Anfrage liefert 409 mit Idempotency-Key has already been used for a different request. Wiederholung löst dieses 409 nie: Eine tatsächlich geänderte Anfrage ist neue Arbeit und benötigt einen neuen Schlüssel.
Warum das nützlich ist
Ein Netzwerktimeout lässt zwei Möglichkeiten offen: Die Anfrage kam nie an, oder sie kam an und nur die Antwort ging verloren. Ohne Idempotenzschlüssel bleiben zwei schlechte Optionen – nicht wiederholen, sodass der Nutzer umsonst wartet, oder wiederholen und möglicherweise doppelt generieren und bezahlen.
Mit Schlüssel ist die Wiederholung sicher: Entweder entsteht der Task oder Sie erhalten den gerade zuvor erstellten zurück.
Welche Endpunkte den Header lesen
| Endpunkt | Wirkung einer Wiederholung mit demselben Schlüssel |
|---|---|
POST /api/v1/jobs/createTask | Ursprünglichen Task in aktuellem Zustand zurückgeben |
POST /api/v1/jobs/retry | Durch Retry erstellten Task zurückgeben. Schlüssel gilt je Ursprungstask; gleicher Schlüssel für unterschiedliche Ursprungstasks kollidiert nicht |
POST /v1/videos | Ursprünglichen Task zurückgeben |
POST /api/v1/jobs/stream und Textendpunkte unter /v1 und /v1beta | 400 zurückgeben; siehe unten |
Leseendpunkte und Aussteller von Upload- und Downloadlinks ignorieren den Header. POST /api/v1/jobs/purge benötigt keinen Schlüssel: Wiederholung für dieselbe taskId ist bereits unproblematisch.
Bei Text und Streaming verhindert der Schlüssel doppelte Belastung, stellt aber verlorene Antworten nicht wieder her
Textantworten werden während der Erzeugung gesendet und nicht gespeichert. Derselbe Schlüssel mit gleichem Inhalt nach abgeschlossener Anfrage liefert 400 (This Idempotency-Key was already used for a completed request, and its answer cannot be replayed. Use a new Idempotency-Key to send a new request) ohne weitere Belastung. Der Text ist nirgendwo abrufbar. Scheiterte die frühere Anfrage, liefert die Wiederholung denselben Fehler; Sie entscheiden dann über einen neuen Versuch mit neuem Schlüssel. Mit anderem Inhalt folgt 409.
Verwenden Sie bei diesen Endpunkten also für jede neue Nachricht einen neuen Schlüssel. Wiederverwenden dürfen Sie ihn nur für exakt dieselbe Anfrage nach Netzwerkfehler, unter Inkaufnahme der verlorenen Antwort. Möchten Sie erneut eine Antwort, senden Sie mit neuem Schlüssel und bezahlen dafür. Siehe Text und Streaming.
Das Zeitfenster beträgt 24 Stunden
Ein Schlüssel gilt 24 Stunden ab der ersten Annahme. Wiederholungen verlängern dies nicht. Danach gilt derselbe Schlüssel als frische Anfrage und erzeugt einen neuen kostenpflichtigen Task. Beenden Sie automatische Wiederholungen deutlich vorher.
Vierundzwanzig Stunden sind ein Kompromiss. Clientwiederholungen dauern Sekunden oder Minuten, die abgedeckt sein müssen. Ein längeres Fenster blockiert die Wiederverwendung länger; mit fest codiertem Schlüssel wäre nur ein Task je Tag möglich.
Schlüssel wählen
- Ein Schlüssel je logischer Anfrage, nicht je HTTP-Aufruf. Wiederholungen müssen denselben verwenden, sonst ist der Mechanismus wirkungslos.
- Leiten Sie ihn aus vorhandener Kennung ab:
order-8814-render-1ist besser als zufällige UUID, weil Sie ihn nach Prozessneustart erneut berechnen können. - Gibt es keine Ableitung, erzeugen Sie vor dem ersten Versuch eine UUID und speichern diese, etwa in der Datenbank neben der Bestellung. Wiederholungen lesen den gespeicherten Wert.
- Schlüssel werden innerhalb Ihres Kontos beansprucht und müssen nicht weltweit eindeutig sein. Jede Wiederholung einer logischen Anfrage muss zusätzlich denselben API-Key verwenden. Maximallänge: 128 Zeichen.
Ein anderer API-Key kann die Anfrage nicht wiederholen
Die Idempotenzzuordnung gilt kontoweit, während Tasksichtbarkeit je API-Key getrennt ist. Ein anderer Key mit bereits beanspruchtem Idempotenzschlüssel erhält 409 Conflict (Idempotency-Key is already in use by this account), ohne ursprüngliche Task-ID. So kann ein Key nicht durch Wiederholung Tasks anderer Integrationen erkunden. Weitere Wiederholung hilft nicht; verwenden Sie den API-Key des ersten Versuchs.
Erzeugen Sie den Schlüssel nie innerhalb der Wiederholungsschleife
// Wrong: a new key per attempt, which defeats the whole mechanism
for (let i = 0; i < 3; i++) {
await create({ 'Idempotency-Key': crypto.randomUUID() });
}
// Right: generate once, outside the loop
const key = crypto.randomUUID();
for (let i = 0; i < 3; i++) {
await create({ 'Idempotency-Key': key });
}Wie eine wiederholte Anfrage aussieht
Bei createTask bleiben taskId und estimatedCost gleich; state ist der aktuelle Zustand des ursprünglichen Tasks: queued, running oder ein Endzustand. Es entstehen kein zweiter Task, keine weitere Reservierung und keine weitere Belastung.
Kein Header oder Feld kennzeichnet die Wiederholung. Für den vollständigen Taskdatensatz verwenden Sie die taskId mit recordInfo.
Gleichzeitiges Senden mit demselben Schlüssel
Erreichen zwei Anfragen desselben API-Keys mit gleichem Idempotenzschlüssel gleichzeitig den Dienst, erzeugt genau eine einen Task; die andere erhält dieselbe taskId. Das beruht auf einer Datenbank-Eindeutigkeitsbedingung statt erst Prüfen und dann Einfügen. Bei gleichzeitiger Vorabprüfung könnten beide Prüfungen leer zurückkommen und zwei Tasks samt Reservierungen entstehen.
Selten erscheint die Konkurrenz als:
{ "code": 409, "msg": "Idempotency key conflict, please retry shortly" }Warten Sie einige hundert Millisekunden und wiederholen mit demselben Schlüssel.
Zwei andere 409-Antworten bei createTask helfen durch Wiederholung nicht: Idempotency-Key has already been used for a different request bei geänderter Anfrage und Idempotency-Key is already in use by this account, wenn ein anderer Kontokey den Schlüssel bereits verwendete. Alle drei teilen code: 409; Ihr Code sollte nicht nach msg verzweigen. Erlauben Sie daher höchstens ein oder zwei 409-Wiederholungen und zeigen dann den Fehler zur menschlichen Prüfung.
Vollständiges Beispiel
Die Funktion erhält den Schlüssel als Argument. Berechnen Sie ihn aus eigenen Datensätzen, hier der Bestell-ID, damit ein neu gestarteter Prozess denselben erneut sendet.
// These are business codes from the response body, not HTTP statuses.
const RETRYABLE = new Set([429, 500, 50301]);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const backoff = (attempt) => sleep(2 ** attempt * 500 + Math.random() * 300);
async function createTaskWithRetry(payload, idempotencyKey, { attempts = 4 } = {}) {
let conflicts = 0;
for (let i = 0; i < attempts; i++) {
// Declared outside try so the checks below can read them.
let res;
let body;
try {
res = await fetch('https://api.spicyapi.ai/api/v1/jobs/createTask', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SPICY_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify(payload),
});
body = await res.json();
} catch {
// Transport failure or unreadable body: the request may well have landed.
// Retrying with the same key is safe.
await backoff(i);
continue;
}
if (res.status === 202 && body.code === 200) return body.data;
// Only one of the three 409s is a transient race, so cap 409 retries at one.
const retry = body.code === 409 ? ++conflicts <= 1 : RETRYABLE.has(body.code);
if (!retry) {
throw new Error(`${body.code} ${body.msg} (request_id=${body.request_id})`);
}
await backoff(i);
}
// Still unknown whether the task exists. Try again later with the SAME key.
throw new Error(`out of retries for Idempotency-Key ${idempotencyKey}`);
}
// payload is { model, input } built from the live catalog; order is your own record.
// The key is derived from the order, so it is identical after a restart.
const task = await createTaskWithRetry(payload, `order-${order.id}-render-1`);
console.log(task.taskId);import os, random, time, requests
# These are business codes from the response body, not HTTP statuses.
RETRYABLE = {429, 500, 50301}
def create_task_with_retry(payload: dict, idempotency_key: str, attempts: int = 4) -> dict:
conflicts = 0
for i in range(attempts):
try:
res = requests.post(
"https://api.spicyapi.ai/api/v1/jobs/createTask",
headers={
"Authorization": f"Bearer {os.environ['SPICY_API_KEY']}",
"Content-Type": "application/json",
"Idempotency-Key": idempotency_key,
},
json=payload,
timeout=30,
)
body = res.json()
except (requests.RequestException, ValueError):
# Transport failure or unreadable body: the request may well have landed.
# Retrying with the same key is safe.
time.sleep(2**i * 0.5 + random.random() * 0.3)
continue
if res.status_code == 202 and body["code"] == 200:
return body["data"]
# Only one of the three 409s is a transient race, so cap 409 retries at one.
if body["code"] == 409:
conflicts += 1
retry = conflicts <= 1
else:
retry = body["code"] in RETRYABLE
if not retry:
raise RuntimeError(f'{body["code"]} {body["msg"]} (request_id={body.get("request_id")})')
time.sleep(2**i * 0.5 + random.random() * 0.3)
# Still unknown whether the task exists. Try again later with the SAME key.
raise RuntimeError(f"out of retries for Idempotency-Key {idempotency_key}")
# payload is {"model": ..., "input": ...} built from the live catalog; order_id is yours.
# The key is derived from the order, so it is identical after a restart.
task = create_task_with_retry(payload, f"order-{order_id}-render-1")
print(task["taskId"])Auch Übertragungsfehler benötigen denselben Schlüssel
Eine ausgelöste fetch-Exception, Verbindungs- oder Lesetimeout: In allen Fällen wissen Sie nicht, ob die Anfrage ankam. Genau dafür gibt es den Idempotenzschlüssel. Wiederholen Sie mit demselben; schlimmstenfalls erhalten Sie den früheren Task. Sind Versuche aufgebraucht, behalten Sie den Schlüssel: Ein späterer Aufruf liefert innerhalb des 24-Stunden-Fensters weiterhin denselben Task.
Webhooks
v1- und v2-Nutzdaten unterscheiden, die richtige Task-ID wählen und Signaturen mit ausführbaren Beispielen für Node.js, Python und curl prüfen.
Kostenschätzungen und Protokollkompatibilität
Den genauen Preis einer Anfrage erfahren, bevor Sie bezahlen, sie zu genau diesem Preis absenden, nach einer verlorenen Antwort sicher weitermachen und wissen, wie die Kompatibilitäts-APIs für Text und Video abrechnen.

