# API `/api/v1` — **eingefroren.** Die vollständige Liste aller 102 Endpunkte samt geforderter Berechtigung steht maschinenlesbar in `apps/api/internal/httpapi/contract_routes.txt`; eine Prüfung schlägt an, sobald sich etwas daran ändert. Der verbindliche Vertrag ist `SYNCOVA_API.md`. Dieses Dokument erklärt, wie die API sich **verhält** — die Dinge, die man aus einer Endpunktliste nicht abliest. ## Die Antworthülle ist maßgeblich, nicht der HTTP-Status ```json { "data": { "…": "…" }, "meta": { "request_id": "…" } } ``` ```json { "error": { "code": "BACKUP_REPOSITORY_UNAVAILABLE", "message": "…", "details": {}, "request_id": "…" } } ``` **Werten Sie `error` gegen `data` aus, nicht `response.ok`.** `GET /health` liefert bei kritischem Zustand `503` **mit** vollständigem `data`-Bericht: Monitoring schlägt an, und die Oberfläche kann trotzdem anzeigen, was kaputt ist. Ein Client, der bei 503 nur „Fehler" meldet, verschenkt genau die Auskunft, die er braucht. Fehlercodes sind sprechende Konstanten in `SCREAMING_SNAKE_CASE`. Sie sind Teil des Vertrags; werten Sie **sie** aus, nicht den Meldungstext — der ist für Menschen und darf sich ändern. `request_id` steht in **jeder** Antwort, auch der erfolgreichen. Bei einer Rückfrage ist sie das Einzige, womit sich eine Anfrage in den Protokollen wiederfinden lässt. ## Anmeldung ```bash curl -X POST https:///api/v1/auth/login \ -H 'Content-Type: application/json' \ -d '{"username":"admin","password":"…"}' ``` ```json { "data": { "mfa_required": false, "tokens": { "access_token": "…", "refresh_token": "…", "access_expires_at": "…", "refresh_expires_at": "…" }, "user": { "…": "…" } } } ``` Bei `"mfa_required": true` folgt `POST /auth/mfa/verify` mit dem Code. **Opake Tokens, keine JWT.** Der Grund ist die sofortige Widerrufbarkeit: Ein JWT bliebe nach Sperre oder Passwortänderung bis zum Ablauf gültig. Gespeichert wird serverseitig nur der SHA-256-Hash. Danach `Authorization: Bearer `. ## Zwei Pflichtfelder im Kopf | Kopfzeile | Wann | Wozu | | --- | --- | --- | | `X-Correlation-ID` | jede Anfrage | verbindet Anfrage und Logzeilen. **Nur eine gültige UUID wird übernommen**, sonst verworfen — damit keine fremden Zeichenketten in die Protokolle geraten | | `Idempotency-Key` | siehe unten | verhindert Doppelausführung | Idempotenz ist erforderlich bei: Auftrag anlegen, Backup starten, Wiederherstellung anlegen, Repository eintragen und destruktiven Konfigurationsänderungen. Ein wiederholter Aufruf mit demselben Schlüssel liefert das erste Ergebnis, statt ein zweites Mal zu handeln. ## Berechtigungen Jeder Endpunkt verlangt genau eine. Sie steht in der eingefrorenen Liste, weil eine stillschweigend gelockerte Prüfung die gefährlichste Änderung überhaupt wäre: Der Endpunkt funktioniert weiter, nur dürfen ihn plötzlich mehr Leute aufrufen. Vier Sonderfälle: - `-` — ohne Anmeldung erreichbar: `/health*`, `/auth/login`, `/auth/refresh`, `/auth/mfa/verify` und `/agents/register` (ein sich aufnehmender Agent besitzt noch kein Betriebstoken). - `sitzung` — angemeldet, ohne besondere Berechtigung: `/me`, `/auth/logout`, MFA-Einrichtung für sich selbst. - `agent-token` — Betriebstoken eines Agenten statt Benutzersitzung. - `restores.overwrite` steckt **nicht** in `restores.execute`. **Berechtigungen im Frontend sind Anzeige, keine Sicherung.** Sie verhindern Sackgassen; geprüft wird auf dem Server. ## Seitenweise Ergebnisse ```text GET /api/v1/backups?page=2&page_size=50 ``` ```json { "data": [ … ], "meta": { "page": 2, "page_size": 50, "total": 312, "request_id": "…" } } ``` ## Dateien Berichte kommen als CSV, JSON oder PDF — **ohne** Antworthülle. Der Fehlerfall dagegen trägt sie. **Prüfen Sie den Inhaltstyp**, bevor Sie speichern. Sonst landet eine Fehlermeldung als `bericht.pdf` im Download-Ordner. ## Die Endpunkte im Überblick | Bereich | Endpunkte | Wofür | | --- | --- | --- | | `/auth`, `/me` | 7 | Anmeldung, Sitzung, eigener zweiter Faktor | | `/users`, `/roles`, `/permissions` | 12 | Konten und Rollen | | `/repositories` | 11 | Eintragen, Zustand, Prüfung, Integritätslauf, Katalogaufbau, Aufbewahrung | | `/jobs` | 8 | Aufträge und deren Läufe | | `/backups` | 8 | Wiederherstellungspunkte, Schutz, Legal Hold, Bewertung | | `/restores` | 6 | Vorabprüfung, Ausführung, Fortsetzung | | `/verification` | 5 | Prüfläufe bis zum Wiederherstellungstest | | `/agents` | 11 | Aufnahme, Verwaltung, Auftragsabholung | | `/proxmox`, `/virtual-machines` | 10 | Verbünde, Bestandsaufnahme, Gäste | | `/alerts`, `/notification-channels` | 8 | Meldungen und Zustellung | | `/metrics`, `/reports`, `/security`, `/audit-events` | 7 | Kennzahlen, Berichte, Sicherheitslage, Protokoll | | `/health` | 3 | Betriebszustand | ## Vier Verhaltensweisen, die überraschen **`POST /jobs/{id}/run` antwortet `202`, nicht `201`.** Der Lauf ist eingereiht; die Sicherung hat nicht begonnen. Ein zweiter Anstoß bei laufendem Auftrag ergibt `409`, nicht `500`. **`POST /repositories` legt nichts an, sondern übernimmt.** Ein Repository entsteht auf einem Datenträger (`syncova-repo create`). Der Endpunkt öffnet das vorhandene, liest dessen Kennung aus dem Descriptor und trägt es ein. Liegt dort keines, wird abgelehnt — ein Eintrag ohne Ablage dahinter wäre ein Ziel, das erst um zwei Uhr nachts als nicht vorhanden auffällt. **Der Integritätslauf meldet einen Befund nicht als Fehler.** `POST /repositories/{id}/integrity-scan` antwortet `200` mit `"healthy": false`. „Die Prüfung schlug fehl" und „das Repository ist beschädigt" sind zwei völlig verschiedene Lagen; sie zu verwechseln macht aus einem Befund einen Werkzeugfehler. **Kennzahlen unterscheiden „null" und „unbekannt".** Jeder Punkt einer Zeitreihe trägt `has_value`. Ein Zeitfenster ohne Sicherungslauf hat *keinen* Durchsatz — nicht null Byte je Sekunde. Zeichnen Sie die Linie unterbrochen, nicht durch den Nullpunkt. ## Was es nicht gibt - **Kein WebSocket/SSE-Stream.** `/api/v1/events/stream` ist im Vertrag vorgesehen und **nicht umgesetzt**. Live-Anzeigen laufen über Abfragen. - **Keine Massenoperationen.** Kein `DELETE /backups?filter=…` — destruktive Handlungen gehen einzeln und werden einzeln auditiert. - **Kein `PUT`.** Änderungen laufen über `PATCH` mit den Feldern, die sich tatsächlich ändern. ## Fehlercodes, die man kennen sollte | Code | Status | Bedeutung | | --- | --- | --- | | `VALIDATION_FAILED` | 422 | fachlich ungültig — die Meldung sagt, was fehlt | | `PERMISSION_DENIED` | 403 | angemeldet, aber nicht berechtigt | | `CONFLICT` | 409 | Zustand passt nicht: Auftrag läuft bereits, Name vergeben, Verbund in Benutzung | | `RATE_LIMITED` | 429 | zu viele Anfragen (Vorgabe 600/min) | | `SERVICE_UNAVAILABLE` | 503 | Abhängigkeit weg. Bei Datenbankausfall ausdrücklich **nicht** „Sitzung abgelaufen" — die Meldung sagt: *„Das ist kein Problem Ihrer Sitzung."* | | `NOT_IMPLEMENTED` | 501 | geplant, aber nicht gebaut. Wird angezeigt statt vorgetäuscht |