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" }
}codeist eine stabile, maschinenlesbare Kennung. Verzweigen Sie darauf — nie auf den Text.requestIdist kurz und zitierfähig. Nennen Sie sie in jeder Support-Anfrage — damit findet Petla den Aufruf in den eigenen Protokollen.fieldssteht nur bei Validierungsfehlern und bildet einen Feldpfad auf einen Grund ab.metaträgt maschinenlesbaren Zusammenhang für die Codes, die welchen haben. Wo es unten erwähnt wird, ist es da.
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.
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.
| Code | HTTP | Was passiert ist | Wiederholen? | Was zu tun ist |
|---|---|---|---|---|
unauthorized | 401 | Kein oder kein bekanntes Bearer-Token. | Nein | Zugangsdaten prüfen. Unverändert zu wiederholen ändert nichts. |
clinic_id_required | 401 | Die Kopfzeile Petla-Clinic-Id fehlt. meta.header nennt sie. | Nein | Petla-Clinic-Id mitsenden — bei jedem Aufruf mit Verbindungstoken. |
rotation_in_progress | 409 | Ein Wechsel der Zugangsdaten wurde angefordert, während das Übergangsfenster des vorherigen noch offen ist. meta.previousCredentialExpiresAt nennt das Ende. | Nein | Nichts 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_mismatch | 403 | Das Token ist gültig, gehört aber zu einer anderen Praxis als die Kopfzeile nennt. | Nein | Die richtige clinicId senden. ⚠️ meta nennt absichtlich nur die Kopfzeile und nie die Praxis, zu der das Token gehört. |
connection_revoked | 401 · 410 | Die 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ören | Aufrufe einstellen. Erst eine neue Kopplung stellt die Verbindung her. |
forbiddennie 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.
| Code | HTTP | Was passiert ist | Wiederholen? | Was zu tun ist |
|---|---|---|---|---|
invalid_body | 400 | Der Rumpf ist überhaupt kein JSON. | Nein | Content-Type und Serialisierung prüfen. |
validation_failed | 400 | Ein Feld fehlt, hat den falschen Typ oder ist im Zusammenhang unzulässig. fields bildet den Feldpfad auf einen Grund ab. | Nein | Auf fields verzweigen, nicht auf den Text. Beim Ack sind die Gründe required_when_accepted, not_allowed_when_accepted und required_when_refused. |
unknown_field | 400 | Der Rumpf trägt einen Schlüssel, den das Schema nicht kennt — meist ein Tippfehler. | Nein | Das unbekannte Feld entfernen. fields nennt es. |
idempotency_key_reused | 400 | Derselbe Idempotency-Key mit einem anderen Rumpf. | Nein | Fü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.
| Code | HTTP | Was passiert ist | Wiederholen? | Was zu tun ist |
|---|---|---|---|---|
capability_not_declared | 400 | Sie rufen etwas auf, das Sie in PUT /capabilities nicht deklariert haben. meta nennt die fehlende Fähigkeit beziehungsweise das fehlende Feld. | Nein | PUT /capabilities aufrufen, dann erneut. ⚠️ 400, nicht 403 — nichts ist verboten, es ist nur nichts angemeldet. |
availability_shape_mismatch | 400 | Sie haben busy deklariert und windows gesendet oder umgekehrt. meta nennt beides. | Nein | Eine Form wählen und dabei bleiben. Die beiden sind keine Varianten derselben Aussage: busy sagt, was belegt ist, windows, was frei ist. |
availability_timezone_mismatch | 400 | Die gesendete Zeitzone ist nicht die der Praxis. | Nein | Die Zone der Praxis verwenden — IANA-Kennung, ⛔ nie einen Offset. |
resource_not_mapped | 404 | Eine resourceId, die kein Mensch einem Kalender zugeordnet hat. | Nein | Bei uns melden. ⛔ Petla legt diese Zuordnung nie automatisch an — dauerhaft, bis ein Mensch sie setzt. |
core_capability_missing | 400 | Ihre Erklärung lässt eine Fähigkeit aus, die SPEC §3.1 Core nennt. meta.missing nennt welche. | Nein | availability.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.
| Code | HTTP | Was passiert ist | Wiederholen? | Was zu tun ist |
|---|---|---|---|---|
already_acknowledged | 409 | Eine zweite Bestätigung, die der gespeicherten widerspricht. | Nein | Eine identische Wiederholung ist kein Fehler: 200 mit Idempotent-Replayed: true. Um einen Termin abzusagen, nicht erneut bestätigen, sondern /cancel aufrufen. |
external_id_in_use | 409 | Ihre externalId ist auf dieser Verbindung bereits einem anderen Termin zugeordnet. | Nein | Eine 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.
| Code | HTTP | Was passiert ist | Wiederholen? | Was zu tun ist |
|---|---|---|---|---|
appointment_not_reschedulable | 409 | Der Termin steht in einem Status, auf den diese Änderung nicht anwendbar ist. meta.status nennt den aktuellen. | Nein | Den 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.
| Code | HTTP | Was passiert ist | Wiederholen? | Was zu tun ist |
|---|---|---|---|---|
appointment_has_no_owner | 409 | Den Termin gibt es, und er hat keinen Tierhalter, an den freigegeben werden könnte. | Nein | Nichts 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_use | 409 | Diese documentId ist auf dieser Verbindung bereits an andere Bytes gebunden. | Nein | Fü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_revoked | 409 | Diese documentId wurde widerrufen. meta.revokedAt nennt wann. | Nein | Die 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_large | 413 | Über 10 MB. meta.maxBytes nennt die Grenze. | Nein | Kleiner ausgeben. ⚠️ Der JSON-Rumpf ist zusätzlich bei 16 MB gedeckelt — dieselbe Grenze vor Base64s Aufblähung um 4/3. |
document_type_unsupported | 415 | Die Bytes sind kein Typ, den Petla ausliefert. meta.detected nennt, was tatsächlich ankam. | Nein | PDF, 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_failed | 502 | Ihre sourceUrl war zulässig, und die Gegenstelle hat nicht mitgespielt — Zeitüberschreitung oder kein 200. | Ja, mit Backoff | Mit 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_invalid | 400 | Die URL, die Sie unter PUT /connections/webhook registrieren wollen, ist eine, an die Petla nicht ausliefert. fields.url nennt, welche Regel verletzt wurde. | Nein | Drei 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_registered | 404 | Auf dieser Verbindung ist kein Webhook-Endpunkt hinterlegt — es gibt nichts zu lesen, zu löschen oder zu rotieren. | Nein | Zuerst 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_unknown | 400 | Die productId benennt kein Impfprodukt, das Petla führt. fields.productId weist darauf hin. | Nein | GET /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_appointment | 400 | Die petlaPetId gehört nicht zu den Patienten dieses Termins. | Nein | Die 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_open | 409 | Die Bestellung ist bereits abgeschlossen — meta.currentStatus nennt den Stand. | Nein | Endstä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.
| Code | HTTP | Was passiert ist | Wiederholen? | Was zu tun ist |
|---|---|---|---|---|
cursor_expired | 410 | Der Cursor ist unbrauchbar. meta.reason ist expired, malformed, wrong-connection oder re-paired. | Nein | Für alle vier dasselbe: Cursor verwerfen und ohne Cursor neu abrufen. Das ist ein anderer Aufruf, keine Wiederholung. |
Kopplung
Nur auf /connections/pairing-codes.
| Code | HTTP | Was passiert ist | Wiederholen? | Was zu tun ist |
|---|---|---|---|---|
pairing_code_expired | 410 | Der Kopplungscode ist abgelaufen, ohne eingelöst worden zu sein. meta.expiresAt nennt wann. | Nein | Einen neuen Code anfordern und der Praxis erneut vorlegen. |
token_already_collected | 410 | Das Verbindungstoken wurde bereits abgeholt. Es wird genau einmal ausgegeben. meta.collectedAt nennt wann. | Nein | Das abgeholte Token speichern. Ist es verloren, neu koppeln. |
pairing_code_invalid | 400 | Der 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. | Nein | Einen neuen Code anfordern. Denselben erneut zu schicken ändert nichts. |
pairing_code_already_redeemed | 400 | Der Code wurde bereits eingelöst. Ein Kopplungscode gilt genau einmal. | Nein | Einen neuen Code anfordern. Besteht die Verbindung schon, koppeln Sie nicht erneut — holen Sie das Token per Abruf. |
Allgemein
Auf jedem Endpunkt möglich.
| Code | HTTP | Was passiert ist | Wiederholen? | Was zu tun ist |
|---|---|---|---|---|
not_found | 404 | Das Objekt gibt es nicht — oder es gehört nicht zu Ihnen. | Nein | Die 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_allowed | 405 | Den Pfad gibt es, dieses Verb nicht. | Nein | Der 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_type | 415 | Der Body ist nicht als JSON deklariert. | Nein | Content-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_limited | 429 | 60 Anfragen pro Minute, je IP und je Endpunkt. | Ja, mit Backoff | Warten und wiederholen. Die Fenster laufen je Endpunkt getrennt. |
internal_error | 500 | Unser Fehler. | Ja, mit Backoff | Mit exponentiell wachsendem Abstand wiederholen. Hält es an, requestId an uns melden. |