Idempotenz
Einen Schreibzugriff mit X-Client-Mutation-Id wiederholen, ohne ein Duplikat zu erzeugen, und eine Änderung mit der optimistischen Sperre X-Base-Version schützen.
Ein Netzwerk kann zwischen dem Senden Ihres Requests und dem Empfang der Antwort ausfallen. Sie wissen dann nicht, ob die Rechnung angelegt wurde. Die Idempotenz beantwortet diese Frage: Sie senden denselben Request mit demselben Schlüssel erneut, und NovaFisko garantiert, dass er nur ein einziges Mal ausgeführt wird.
Der Schlüssel X-Client-Mutation-Id
Fügen Sie den Header X-Client-Mutation-Id jedem schreibenden Request (POST, PUT, PATCH, DELETE) hinzu, der sich auf ein Mandat bezieht, also unter companies/{company} liegt.
curl -X POST https://api.novafisko.com/v1/companies/k3Jd9fPq2LmX/entries/invoice \
-H "Authorization: Bearer $NOVAFISKO_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Client-Mutation-Id: 0b0f7a52-63f4-4f1f-8a43-0d4f6b0b8a10" \
-d '{
"journal_id": 3,
"third_party_id": 41,
"entry_date": "2026-10-02",
"reference": "F-2026-0918",
"lines": [{"account": "604000", "amount": "1250.00", "vat_code": "A21"}]
}'
Regeln
| Regel | Einzelheiten |
|---|---|
| Format | Freie Zeichenfolge mit 1 bis 64 Zeichen. Eine UUID Version 4 eignet sich bestens |
| Geltungsbereich | Der Schlüssel ist pro Mandat eindeutig |
| Speicherung | Nur erfolgreiche Antworten (Status 2xx) werden gespeichert |
| Betroffene Requests | POST, PUT, PATCH, DELETE unter companies/{company} |
| Ausnahme | sync/push verwaltet seine eigene Idempotenz, Mutation für Mutation |
Ein leerer Schlüssel oder einer mit mehr als 64 Zeichen wird ignoriert: Der Request wird normal ausgeführt, ohne Schutz.
Erster Versand
Der Request wird ausgeführt. Ist er erfolgreich, speichert NovaFisko den Status und den Body der Antwort.
HTTP/1.1 201 Created
X-Operation-Id: 5d0f6a7e-2f0b-4a35-9b56-7f1f3c1f4c22
Wiederholung
Wenn Sie denselben Schlüssel für dasselbe Mandat erneut senden, wird nichts ausgeführt. Sie erhalten den gespeicherten Body mit dem Status 200 und einem Header, der die Wiederholung anzeigt.
HTTP/1.1 200 OK
X-Idempotent-Replay: 1
Content-Type: application/json
Eine Wiederholung antwortet immer mit 200, auch wenn der ursprüngliche Request mit 201 oder 204 geantwortet hatte. Betrachten Sie jeden Status 2xx als Erfolg und prüfen Sie X-Idempotent-Replay, wenn Sie die beiden Fälle unterscheiden müssen.
Was nicht gespeichert wird
Ein fehlgeschlagener Request (4xx, 5xx) wird nicht gespeichert. Sie können die Daten korrigieren und denselben Schlüssel erneut senden oder es nach einem Fehler 500 oder 503 unverändert noch einmal versuchen.
Der gespeicherte Body ist auf 500 KB begrenzt. Darüber hinaus liefert die Wiederholung 200 mit einem Body null: Der Vorgang hat stattgefunden, lesen Sie die Ressource erneut, um ihren Zustand zu erhalten.
Den Schlüssel richtig wählen
- Erzeugen Sie den Schlüssel vor dem ersten Versand und speichern Sie ihn zusammen mit dem Vorgang in Ihrem System.
- Ein Schlüssel bezeichnet eine Absicht, keinen Versuch: Alle Versuche desselben Vorgangs teilen sich denselben Schlüssel.
- Verwenden Sie einen Schlüssel niemals für einen anderen Vorgang wieder. Der Server vergleicht die Bodys nicht: Er würde das Ergebnis des ersten zurückgeben.
Ein deterministischer Schlüssel funktioniert sehr gut, wenn Ihr System bereits eine stabile Kennung besitzt:
// One key per business operation, stable across retries
$key = substr(hash('sha256', 'erp-atelier:purchase-invoice:'.$invoice->id), 0, 64);
Beispiel einer Wiederholungsschleife
import { randomUUID } from "node:crypto";
async function postOnce(url, body, token) {
const mutationId = randomUUID(); // generated once, reused on every retry
for (let attempt = 0; attempt < 5; attempt++) {
try {
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
Accept: "application/json",
"X-Client-Mutation-Id": mutationId,
},
body: JSON.stringify(body),
});
if (response.status === 429 || response.status >= 500) {
const wait = Number(response.headers.get("Retry-After") ?? 2 ** attempt);
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
continue;
}
return {
replayed: response.headers.get("X-Idempotent-Replay") === "1",
status: response.status,
data: await response.json(),
};
} catch (networkError) {
await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000));
}
}
throw new Error("NovaFisko API unreachable");
}
Optimistische Sperre mit X-Base-Version
Die Idempotenz schützt vor Ihren eigenen Duplikaten. Die optimistische Sperre schützt vor den Änderungen der anderen: Sie verhindert, dass eine Änderung überschrieben wird, die zwischen Ihrem Lese- und Ihrem Schreibzugriff erfolgt ist.
Jede vom Verlauf erfasste Ressource stellt eine Ganzzahl version bereit. Senden Sie den gelesenen Wert im Header X-Base-Version eines PATCH, eines PUT oder eines DELETE.
curl -X PATCH https://api.novafisko.com/v1/companies/k3Jd9fPq2LmX/third-parties/41 \
-H "Authorization: Bearer $NOVAFISKO_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Base-Version: 3" \
-d '{"email": "compta@brasserie-collines.example"}'
Ist die Version des Servers weiterhin 3, wird die Änderung angewendet. Hat jemand den Geschäftspartner inzwischen geändert, bleibt alles unberührt und die API antwortet mit 409:
{
"message": "Cet enregistrement a été modifié entre-temps.",
"code": "conflict",
"server_version": 5,
"server_data": {
"id": 41,
"type": "supplier",
"name": "Brasserie des Collines SA",
"email": "facturation@brasserie-collines.example",
"version": 5
}
}
So lösen Sie den Konflikt: Vergleichen Sie server_data mit Ihren Werten, entscheiden Sie, was erhalten bleiben soll, und senden Sie den Request anschließend mit X-Base-Version: 5 erneut.
| Punkt | Verhalten |
|---|---|
| Aktivierung | Nur wenn der Header vorhanden und numerisch ist |
| Methoden | PATCH, PUT, DELETE. Bei POST ignoriert |
| Vergleich | Konflikt, wenn die Version des Servers strikt größer ist als Ihre |
| Betroffene Ressource | Der durch die URL bezeichnete Datensatz (niemals das Mandat selbst) |
Die Versionsprüfung erfolgt vor der idempotenten Wiederholung. Kombinieren Sie beide Header unbesorgt: X-Base-Version schützt die Daten, X-Client-Mutation-Id schützt den Versuch.
Und bei Offline-Stapeln
POST sync/push wendet dieselben Grundsätze auf einen Stapel von Mutationen an: Jede Mutation trägt ihre client_mutation_id und, wenn Sie es wünschen, ihre base_version. Eine bereits verarbeitete Mutation kommt mit dem Status duplicate und ihrem ursprünglichen Ergebnis zurück. Konflikte werden dort Feld für Feld zusammengeführt, statt abgelehnt zu werden. Siehe Fortlaufend synchronisieren.