Fehlercodes

Jeder Code, den die Partner-API ausgibt: was passiert ist, ob eine Wiederholung hilft und was zu tun ist.

{
  "code": "validation_failed",
  "status": 400,
  "requestId": "K4M2P7RX",
  "fields": { "externalId": "required_when_accepted" }
}
Zwei Namensräume, zwei Schreibweisen

code ist lower_snake_case; der Ablehnungsgrund reason beim Ack ist UPPER_SNAKE_CASE. Kein Wert gehört zu beiden.

SLOT_TAKEN ist ein Grund, den Ihr System in einem Ack-Rumpf sendet. Als code in einer Fehlerantwort kann er nie erscheinen.

Achtung

Offene Aufzählung. Behandeln Sie einen unbekannten Code als allgemeinen Fehlschlag seiner HTTP-Klasse. Petla darf einen Code ergänzen, ohne dass das eine brechende Änderung ist.

Und umgekehrt: drei der Codes unten sind deklariert und werden heute von keiner Route ausgegeben. Sie stehen hier, weil ein Partner, der einen Behandlungszweig dafür schreibt, das erfahren sollte, statt es aus einer Lücke zu schließen.

Authentifizierung und Verbindung

Geprüft, bevor der Rumpf überhaupt gelesen wird. Kein Code hier wird durch eine Wiederholung besser.

CodeHTTPWas passiert istWiederholen?Was zu tun ist
unauthorized401Kein oder kein bekanntes Bearer-Token.NeinZugangsdaten prüfen. Unverändert zu wiederholen ändert nichts.
clinic_id_required401Die Kopfzeile Petla-Clinic-Id fehlt. meta.header nennt sie.NeinPetla-Clinic-Id mitsenden — bei jedem Aufruf mit Verbindungstoken.
rotation_in_progress409Ein Wechsel der Zugangsdaten wurde angefordert, während das Übergangsfenster des vorherigen noch offen ist. meta.previousCredentialExpiresAt nennt das Ende.NeinNichts tun — Ihre bisherigen Zugangsdaten funktionieren noch. Genau dafür ist diese Absage da: Der Endpunkt gibt ein Geheimnis nur einmal heraus, ein bedienter Wiederholungsversuch würde also jene Zugangsdaten außer Kraft setzen, die Sie nachweislich noch besitzen. Haben Sie die neuen wirklich verloren, schreiben Sie uns (§15).
connection_clinic_mismatch403Das Token ist gültig, gehört aber zu einer anderen Praxis als die Kopfzeile nennt.NeinDie richtige clinicId senden. ⚠️ meta nennt absichtlich nur die Kopfzeile und nie die Praxis, zu der das Token gehört.
connection_revoked401 · 410Die Praxis hat getrennt — oder Vendor bzw. Praxis wurden deaktiviert. 410 nur beim Abrufen des Pairing-Codes: eingelöst und vor der Token-Abholung wieder getrennt.Nein — aufhörenAufrufe einstellen. Erst eine neue Kopplung stellt die Verbindung her.
forbidden
nie ausgegeben
—Deklariert, von keiner Route ausgegeben.—Nichts zu tun. Der Code steht hier, damit seine Abwesenheit belegt ist.

Rumpf und Validierung

Der Aufruf ist erreicht, die Nutzlast trägt einen Fehler.

CodeHTTPWas passiert istWiederholen?Was zu tun ist
invalid_body400Der Rumpf ist überhaupt kein JSON.NeinContent-Type und Serialisierung prüfen.
validation_failed400Ein Feld fehlt, hat den falschen Typ oder ist im Zusammenhang unzulässig. fields bildet den Feldpfad auf einen Grund ab.NeinAuf fields verzweigen, nicht auf den Text. Beim Ack sind die Gründe required_when_accepted, not_allowed_when_accepted und required_when_refused.
unknown_field400Der Rumpf trägt einen Schlüssel, den das Schema nicht kennt — meist ein Tippfehler.NeinDas unbekannte Feld entfernen. fields nennt es.
idempotency_key_reused400Derselbe Idempotency-Key mit einem anderen Rumpf.NeinFür einen neuen Rumpf einen neuen Schlüssel. Ein identischer Rumpf liefert 200 mit Idempotent-Replayed: true.

Fähigkeiten und Zuordnung

Der Aufruf ist wohlgeformt und die Verbindung hat nicht angemeldet, ihn zu benutzen.

CodeHTTPWas passiert istWiederholen?Was zu tun ist
capability_not_declared400Sie rufen etwas auf, das Sie in PUT /capabilities nicht deklariert haben. meta nennt die fehlende Fähigkeit beziehungsweise das fehlende Feld.NeinPUT /capabilities aufrufen, dann erneut. ⚠️ 400, nicht 403 — nichts ist verboten, es ist nur nichts angemeldet.
availability_shape_mismatch400Sie haben busy deklariert und windows gesendet oder umgekehrt. meta nennt beides.NeinEine Form wählen und dabei bleiben. Die beiden sind keine Varianten derselben Aussage: busy sagt, was belegt ist, windows, was frei ist.
availability_timezone_mismatch400Die gesendete Zeitzone ist nicht die der Praxis.NeinDie Zone der Praxis verwenden — IANA-Kennung, ⛔ nie einen Offset.
resource_not_mapped404Eine resourceId, die kein Mensch einem Kalender zugeordnet hat.NeinBei uns melden. ⛔ Petla legt diese Zuordnung nie automatisch an — dauerhaft, bis ein Mensch sie setzt.
core_capability_missing400Ihre Erklärung lässt eine Fähigkeit aus, die SPEC §3.1 Core nennt. meta.missing nennt welche.Neinavailability.publish, appointments.receive und appointments.acknowledge mitsenden. ⚠️ Nicht dasselbe wie capability_not_declared: dort weist ein Endpunkt Arbeit zurück, die Sie nie erklärt haben — hier ist die Erklärung selbst nicht regelkonform.

Bestätigung

Nur auf POST /appointments/{appointmentId}/ack.

CodeHTTPWas passiert istWiederholen?Was zu tun ist
already_acknowledged409Eine zweite Bestätigung, die der gespeicherten widerspricht.NeinEine identische Wiederholung ist kein Fehler: 200 mit Idempotent-Replayed: true. Um einen Termin abzusagen, nicht erneut bestätigen, sondern /cancel aufrufen.
external_id_in_use409Ihre externalId ist auf dieser Verbindung bereits einem anderen Termin zugeordnet.NeinEine eigene Kennung je Termin. Petla führt die Zuordnung in beiden Richtungen, damit spätere Nachrichten nie eine Kennung nennen, die die Praxis am falschen Termin sieht.

Änderungen aus dem PVS

Auf POST /appointments/{appointmentId}/reschedule und /cancel.

CodeHTTPWas passiert istWiederholen?Was zu tun ist
appointment_not_reschedulable409Der Termin steht in einem Status, auf den diese Änderung nicht anwendbar ist. meta.status nennt den aktuellen.NeinDen eigenen Zustand abgleichen, nicht wiederholen. ⚠️ Ein 409 und kein 400: der Rumpf ist in Ordnung und wäre eine Minute früher angenommen worden — der Termin hat sich inzwischen bewegt, typischerweise am Tresen.

Dokumente

Auf POST /appointments/{appointmentId}/documents und dem zugehörigen /revoke. Beide setzen die Fähigkeit documents.push voraus; ohne sie antworten beide mit capability_not_declared.

CodeHTTPWas passiert istWiederholen?Was zu tun ist
appointment_has_no_owner409Den Termin gibt es, und er hat keinen Tierhalter, an den freigegeben werden könnte.NeinNichts zu tun — das ist ein echter Zustand und kein Fehler. Ein am Tresen mit Namen und ohne Konto angelegter Laufkunde ist genau das.
document_id_in_use409Diese documentId ist auf dieser Verbindung bereits an andere Bytes gebunden.NeinFür das korrigierte Dokument eine neue Kennung vergeben. ⚠️ Ein erneutes Senden derselben Bytes ist kein Fehler, sondern 200 — sonst hielte Ihr Client die Wiederholung nach einer Zeitüberschreitung für gescheitert.
document_revoked409Diese documentId wurde widerrufen. meta.revokedAt nennt wann.NeinDie Wiederholung aus der eigenen Warteschlange nehmen. ⛔ Genau dafür ist der Vermerk da: sonst landete eine noch offene Wiederholung Minuten nach dem Widerruf in der App des Halters.
document_too_large413Über 10 MB. meta.maxBytes nennt die Grenze.NeinKleiner ausgeben. ⚠️ Der JSON-Rumpf ist zusätzlich bei 16 MB gedeckelt — dieselbe Grenze vor Base64s Aufblähung um 4/3.
document_type_unsupported415Die Bytes sind kein Typ, den Petla ausliefert. meta.detected nennt, was tatsächlich ankam.NeinPDF, JPEG, PNG oder WebP senden. ⛔ Der Typ wird aus der Signatur gelesen, nicht aus Ihrer Angabe. ⚠️ Nicht zu verwechseln mit unsupported_media_type, das die Kopfzeile Content-Type der Anfrage betrifft.
document_fetch_failed502Ihre sourceUrl war zulässig, und die Gegenstelle hat nicht mitgespielt — Zeitüberschreitung oder kein 200.Ja, mit BackoffMit Backoff wiederholen. ⚠️ Ein 5xx mit Absicht: eine URL, die Petla von vornherein ablehnt, ist Ihre Konfiguration und kommt als 400 mit fields.sourceUrl zurück — ein wackliger Objektspeicher ist es nicht.
webhook_url_invalid400Die URL, die Sie unter PUT /connections/webhook registrieren wollen, ist eine, an die Petla nicht ausliefert. fields.url nennt, welche Regel verletzt wurde.NeinDrei Regeln, drei verschiedene Gründe: nur https (ein Webhook-Body trägt Kund:innen- und Patientendaten), keine Zugangsdaten in der Adresse (user:pass@… landet in jeder Logzeile, die das Ziel protokolliert — auch in unserer), und keine Loopback- oder privaten Adressen (Petlas Server stellt die Anfrage, also wäre das ein Weg zu Zielen, die nur wir erreichen). ⚠️ Eigener Code statt validation_failed: Ihr JSON kann völlig korrekt sein und die URL trotzdem unzustellbar.
webhook_not_registered404Auf dieser Verbindung ist kein Webhook-Endpunkt hinterlegt — es gibt nichts zu lesen, zu löschen oder zu rotieren.NeinZuerst PUT /connections/webhook. ⚠️ Bewusst nicht not_found: das bedeutet in dieser API einen Termin oder ein Dokument, und wer es hier läse, würde eine gelöschte Verbindung suchen, die es nicht gibt.
vaccine_product_unknown400Die productId benennt kein Impfprodukt, das Petla führt. fields.productId weist darauf hin.NeinGET /vaccine-products erneut lesen und Ihre eigene Artikelnummer darauf abbilden. ⚠️ Eigener Code statt validation_failed, weil die Antwort immer dieselbe eine Anweisung ist — Ihr JSON ist in Ordnung, die Zuordnung ist es nicht. ⛔ Ein Produktname ist nie zulässig: eine Zeichenkette bildet auf keine Antigene ab, und Petla berechnet die Erinnerung je Antigen.
patient_not_on_appointment400Die petlaPetId gehört nicht zu den Patienten dieses Termins.NeinDie Id aus patients[].petlaPetId genau dieses Termins verwenden. ⛔ Für ein Tier, das es nicht gibt, und für eines, das es in einer anderen Praxis gibt, lautet die Antwort identisch— zwei Antworten würden den Endpunkt zu einem Orakel dafür machen, ob eine Id in Petla ein Tier ist.
medication_order_not_open409Die Bestellung ist bereits abgeschlossen — meta.currentStatus nennt den Stand.NeinEndstände sind PICKED_UP, DECLINED und CANCELLED; aus ihnen führt kein Weg zurück. ⚠️ Denselben Stand noch einmal zu melden ist kein Fehler, sondern ein 200 mit Idempotent-Replayed: true — die Wiederholung nach einer Zeitüberschreitung darf nicht wie ein gescheiterter Schreibvorgang aussehen. Lesen Sie den aktuellen Stand über GET /medication-orders: die Praxis kann dieselbe Bestellung auch in Petla bearbeitet haben.

Ereignisstrom

Nur auf GET /events.

CodeHTTPWas passiert istWiederholen?Was zu tun ist
cursor_expired410Der Cursor ist unbrauchbar. meta.reason ist expired, malformed, wrong-connection oder re-paired.NeinFür alle vier dasselbe: Cursor verwerfen und ohne Cursor neu abrufen. Das ist ein anderer Aufruf, keine Wiederholung.

Kopplung

Nur auf /connections/pairing-codes.

CodeHTTPWas passiert istWiederholen?Was zu tun ist
pairing_code_expired410Der Kopplungscode ist abgelaufen, ohne eingelöst worden zu sein. meta.expiresAt nennt wann.NeinEinen neuen Code anfordern und der Praxis erneut vorlegen.
token_already_collected410Das Verbindungstoken wurde bereits abgeholt. Es wird genau einmal ausgegeben. meta.collectedAt nennt wann.NeinDas abgeholte Token speichern. Ist es verloren, neu koppeln.
pairing_code_invalid400Der Code ist unlesbar, unbekannt, abgelaufen im Sinne der Versuchszählung, oder der Anbieter ist abgeschaltet. Alle vier ergeben dieselbe Antwort, damit Raten nichts über Alphabet, Länge oder Existenz verrät.NeinEinen neuen Code anfordern. Denselben erneut zu schicken ändert nichts.
pairing_code_already_redeemed400Der Code wurde bereits eingelöst. Ein Kopplungscode gilt genau einmal.NeinEinen neuen Code anfordern. Besteht die Verbindung schon, koppeln Sie nicht erneut — holen Sie das Token per Abruf.

Allgemein

Auf jedem Endpunkt möglich.

CodeHTTPWas passiert istWiederholen?Was zu tun ist
not_found404Das Objekt gibt es nicht — oder es gehört nicht zu Ihnen.NeinDie beiden sind absichtlich nicht zu unterscheiden: ein 403 würde bestätigen, dass die Kennung existiert, und diesen Endpunkt zu einem Orakel darüber machen, welche Praxis ein Mitbewerber gerade koppelt.
method_not_allowed405Den Pfad gibt es, dieses Verb nicht.NeinDer Allow-Header nennt die erlaubten Verben, und meta.allow wiederholt sie im Body. ⚠️ In openapi.yaml ist dieser Code bei keiner Operation deklariert — ein generierter Client kann ein Verb, das dort nicht steht, gar nicht aufrufen. Sie treffen ihn also nur bei einer von Hand geschriebenen Anfrage.
unsupported_media_type415Der Body ist nicht als JSON deklariert.NeinContent-Type: application/json setzen — mit oder ohne charset, ein +json-Suffix zählt auch. meta.received enthält, was tatsächlich ankam. ⚠️ fetch(url, { body: JSON.stringify(x) }) sendet ohne eigenen Header text/plain — das wurde früher angenommen und gespeichert, sodass ein Client bei uns funktionierte und beim ersten strengen Proxy im Netz Ihres Kunden scheiterte.
rate_limited42960 Anfragen pro Minute, je IP und je Endpunkt.Ja, mit BackoffWarten und wiederholen. Die Fenster laufen je Endpunkt getrennt.
internal_error500Unser Fehler.Ja, mit BackoffMit exponentiell wachsendem Abstand wiederholen. Hält es an, requestId an uns melden.