# Fehler

> Wie die API Fehler als RFC 9457 Problem Details mit stabilen Codes und einer Request-ID meldet.

Jede Fehlerantwort hat einen Status `4xx` oder `5xx` und einen Body vom Typ `application/problem+json`
(RFC 9457 Problem Details). Ihr Code entscheidet anhand des HTTP-Status und des stabilen `code`; die Texte sind
für Menschen gedacht.

## Felder eines Problems

- **type** `string` (erforderlich): Problemtyp. `about:blank`, sofern kein eigener Typ dokumentiert ist.
- **title** `string` (erforderlich): Kurze Zusammenfassung; bei `about:blank` die HTTP-Statusbezeichnung, z. B. `Bad Request`.
- **status** `integer` (erforderlich): Der HTTP-Statuscode, im Body wiederholt.
- **code** `string` (erforderlich): Stabiler, maschinenlesbarer Code in snake_case, z. B. `validation_error`. Siehe [Codes](#codes).
- **detail** `string`: Erklärung dieses Einzelfalls, auf Englisch. Gut fürs Log, aber nicht zum Auswerten.
- **instance** `string`: URI-Referenz der Anfrage, die das Problem ausgelöst hat, falls vorhanden.
- **request_id** `string`: Die ID der Anfrage, gleich dem Response-Header `X-Request-Id`. Nennen Sie sie, wenn Sie den Support
  kontaktieren.
- **errors** `object[]`: Angaben je Feld bei `validation_error` und bei `conflict`, wenn ein Wert eindeutig sein muss.

  **Feldfehler**
  - **field** `string` (erforderlich): Das Body-Feld (mit Punkten und Listenpositionen, z. B. `values[0].measured_at`), der Name des
    Query-Parameters oder Headers (z. B. `limit`) oder `body`, wenn der Body als Ganzes ungültig ist
    (fehlerhaftes JSON, eine unbekannte Eigenschaft, ein falscher Content-Type).
  - **code** `string` (erforderlich): Stabiler Grund, siehe [Codes für Feldfehler](#codes-für-feldfehler).
  - **message** `string` (erforderlich): Erklärung auf Englisch; übersetzen Sie anhand von `code`.

## Codes für Feldfehler

| Code                  | Bedeutung                                                                                                                                        |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `required`            | Ein Pflichtfeld, ein Pflichtparameter oder ein Pflichtheader fehlt.                                                                              |
| `too_long`            | Ein Text ist länger als erlaubt.                                                                                                                 |
| `too_short`           | Ein Text ist kürzer als erlaubt.                                                                                                                 |
| `out_of_range`        | Eine Zahl, ein Datum oder ein Zeitpunkt liegt außerhalb des erlaubten Bereichs, z. B. ein `measured_at` in der Zukunft.                          |
| `invalid_format`      | Der Wert hat das falsche Format (Datum, UUID, Muster), oder der Body ist kein gültiges JSON bzw. nicht `application/json`.                       |
| `invalid_check_digit` | Die Prüfziffer einer GTIN oder GLN stimmt nicht.                                                                                                 |
| `invalid_value`       | Der Wert ist hier nicht erlaubt: ein unbekannter Enum-Wert, ein ungültiger Parameter oder Cursor, eine fehlschlagende feldübergreifende Prüfung. |
| `unknown_field`       | Das Feld ist hier unbekannt, z. B. ein Feld, das keine Zustandsangabe der Einheit ist, oder eine unbekannte Eigenschaft im Body.                 |
| `already_exists`      | Ein Wert, der eindeutig sein muss, ist schon vergeben (zusammen mit `conflict`).                                                                 |

## Beispiele

Eine Zustandsmeldung mit einem Messzeitpunkt in der Zukunft
([`updateCondition`](https://passport.kabin.sh/docs/de/api-reference/lifecycle/update-condition)) wird als Ganzes abgelehnt:

```json title="400"
{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "code": "validation_error",
  "detail": "The request failed validation.",
  "request_id": "4f9c2a7d1e0b5c8a3f6e2d19",
  "errors": [
    {
      "field": "values[0].measured_at",
      "code": "out_of_range",
      "message": "measured_at lies in the future"
    }
  ]
}
```

Zu viele Anfragen: Der Header `Retry-After` nennt die Wartezeit in Sekunden.

```http title="429"
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 17
X-Request-Id: 8b1e6f03c2d94a7e5f0a1b2c

{
  "type": "about:blank",
  "title": "Too Many Requests",
  "status": 429,
  "code": "rate_limited",
  "detail": "Too many attempts. Try again later.",
  "request_id": "8b1e6f03c2d94a7e5f0a1b2c"
}
```

## Codes

Die Werte von `ProblemCode` im veröffentlichten Vertrag. Codes mit dem Hinweis „Nur im Portal“ kommen mit einem
API-Schlüssel nicht vor; sie stehen hier, weil der Vertrag sie enthält.

| Code                         | HTTP-Status | Bedeutung                                                                                                                                                         |
| ---------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `validation_error`           | 400         | Die Anfrage ist ungültig. \`errors\[]\` nennt jedes betroffene Feld, jeden Query-Parameter oder Header mit einem stabilen Fehlercode.                             |
| `invalid_token`              | 400         | Nur im Portal: ein E-Mail- oder Einladungslink ist unbekannt, verbraucht oder abgelaufen.                                                                         |
| `unauthenticated`            | 401         | Kein gültiger API-Schlüssel: der Header fehlt, der Schlüssel ist unbekannt oder widerrufen, oder die Anfrage trägt zugleich ein Sitzungs-Cookie.                  |
| `invalid_credentials`        | 401         | Nur im Portal: E-Mail-Adresse oder Passwort bei der Anmeldung sind falsch.                                                                                        |
| `forbidden`                  | 403         | Der Schlüssel hat nicht den Scope, den die Operation verlangt, oder die Rechte eines Betrachters reichen nicht aus.                                               |
| `email_not_verified`         | 403         | Nur im Portal: die E-Mail-Adresse ist noch nicht bestätigt.                                                                                                       |
| `not_found`                  | 404         | Die Ressource gibt es nicht, oder sie gehört zu einer anderen Organisation.                                                                                       |
| `method_not_allowed`         | 405         | Der Pfad existiert, aber nicht mit dieser HTTP-Methode.                                                                                                           |
| `conflict`                   | 409         | Die Anfrage widerspricht dem aktuellen Zustand, zum Beispiel ist das Batteriemodell der Einheit archiviert: ihre Daten lassen sich lesen, aber nicht mehr ändern. |
| `identifiers_locked`         | 409         | Nur im Portal: Kennungen einer veröffentlichten Einheit lassen sich nicht mehr ändern.                                                                            |
| `payload_too_large`          | 413         | Der Request-Body ist größer als erlaubt (standardmäßig 1 MiB).                                                                                                    |
| `publish_blocked`            | 422         | Nur im Portal: die Veröffentlichung scheitert an offenen Pflichtangaben; \`blockers\[]\` nennt sie.                                                               |
| `rate_limited`               | 429         | Zu viele Anfragen. Warten Sie die Sekunden aus dem Header \`Retry-After\` ab und versuchen Sie es dann erneut.                                                    |
| `request_closed`             | 410         | Nur für Lieferanten- und Servicepartner-Links: der Link ist abgelaufen, zurückgezogen oder verbraucht.                                                            |
| `internal_error`             | 500         | Ein Fehler auf unserer Seite. Wiederholen Sie die Anfrage später; nennen Sie dem Support die \`request_id\`.                                                      |
| `address_lookup_unavailable` | 503         | Nur im Portal: die Adresssuche ist gerade nicht erreichbar; die Adresse lässt sich von Hand eingeben.                                                             |

## Request-ID

Jede Antwort, ob erfolgreich oder nicht, trägt einen Header `X-Request-Id`. In einem Problem hat
`request_id` denselben Wert. Der Server vergibt die ID selbst und ignoriert eine mitgesendete. Protokollieren
Sie sie bei jeder fehlgeschlagenen Anfrage und nennen Sie sie, wenn Sie den
[Support](https://passport.kabin.sh/docs/de/general/support) kontaktieren.

## Fehler behandeln

- Entscheiden Sie anhand von `status` und `code`, nie anhand von `title`, `detail` oder `message`: Diese Texte
  können sich ändern.
- Rechnen Sie mit neuen Codes. Behandeln Sie einen unbekannten `code` nach seiner HTTP-Statusklasse.
- Beheben Sie `400`, `401`, `403`, `404` und `409`, statt die Anfrage unverändert zu wiederholen.
- Wiederholen Sie `429` nach `Retry-After`, `5xx` und Netzwerkfehler mit exponentiellem Backoff. Bevor Sie
  einen Schreibzugriff wiederholen, lesen Sie [Wiederholungen und Idempotenz](https://passport.kabin.sh/docs/de/idempotency).
- Antworten enthalten nie SQL, Stacktraces oder Fehler von Drittsystemen; ein unerwarteter Fehler ist
  `500 internal_error` mit Request-ID.

Die Anleitung [Fehler behandeln](https://passport.kabin.sh/docs/de/guides/handle-errors) zeigt eine vollständige Fehlerbehandlung in
TypeScript, Python und Go.
