# Exercises API Specification **Version:** 1.5 **Datum:** 2026-05-08 **Status:** Teilweise implementiert (Liste mit Filtern + Varianten + Medienlimits + Progressionsgraphen siehe Code) **Autor:** Claude Code **Änderungen v1.4:** Endpoints **`/exercise-progression-graphs`** inkl. Kanten, **`POST …/edges/sequence`**, **`POST …/edges/delete-batch`** — Detailtabellen siehe **`TRAINING_FRAMEWORK_SPEC.md`** §3.3 **Änderungen v1.5:** Medien-/Inline-Workflow aktualisiert (Modal-Picker, Drag&Drop UX im Frontend), Klarstellung zu `context` (legacy/optional), Hinweise zu Platzhaltern in Rich-Text-Feldern. **Änderungen v1.3:** `GET /exercises` erweiterte Query-Parameter (`include_variants`, Multi-Filter, `ai_search`-Platzhalter); Dokumentation angepasst **Änderungen v1.2:** KI-Assistenz Endpoints, Skill-Level-System (benannte Stufen), intensity als low/medium/high **Änderungen v1.1:** Exercise Blocks Endpoints, Permissions dokumentiert, age_groups korrigiert --- ## Base URL ``` Production: https://shinkan.jinkendo.de/api Development: https://dev.shinkan.jinkendo.de/api ``` --- ## Authentication **Header:** `X-Auth-Token: ` **Alle Endpoints** (außer `/auth/*`) erfordern Authentication. --- ## Endpoints Overview | Method | Endpoint | Beschreibung | |--------|----------|--------------| | **Exercises** | | GET | `/exercises` | List exercises mit Filtern | | GET | `/exercises/{id}` | Exercise Detail mit Enrichment | | POST | `/exercises` | Create exercise | | PUT | `/exercises/{id}` | Update exercise | | DELETE | `/exercises/{id}` | Delete exercise | | **Variants** | | POST | `/exercises/{id}/variants` | Create variant | | PUT | `/exercises/{id}/variants/{variant_id}` | Update variant | | DELETE | `/exercises/{id}/variants/{variant_id}` | Delete variant | | PUT | `/exercises/{id}/variants/reorder` | Reorder variants (DnD) | | **Media** | | POST | `/exercises/{id}/media` | Upload/Embed media | | PUT | `/exercises/{id}/media/{media_id}` | Update media metadata | | DELETE | `/exercises/{id}/media/{media_id}` | Delete media | | PUT | `/exercises/{id}/media/reorder` | Reorder media (DnD) | | **KI-Assistenz** | | POST | `/exercises/ai/suggest` | KI-Vorschläge (Summary + Skills) für neues Formular | | POST | `/exercises/{id}/ai/regenerate` | KI-Vorschläge neu generieren für bestehende Übung | | **Exercise Blocks** | | GET | `/exercise-blocks` | List blocks (mit Filtern) | | GET | `/exercise-blocks/{id}` | Block Detail mit Items | | POST | `/exercise-blocks` | Create block | | PUT | `/exercise-blocks/{id}` | Update block | | DELETE | `/exercise-blocks/{id}` | Delete block | | POST | `/exercise-blocks/{id}/items` | Add item to block | | PUT | `/exercise-blocks/{id}/items/{item_id}` | Update item | | DELETE | `/exercise-blocks/{id}/items/{item_id}` | Remove item | | PUT | `/exercise-blocks/{id}/items/reorder` | Reorder items (DnD) | | **Progressionsgraphen** (Übung→Übung) | | GET | `/exercise-progression-graphs` | Liste Graphen | | GET | `/exercise-progression-graphs/{id}` | Detail; Query `include_edges` | | POST | `/exercise-progression-graphs` | Graph anlegen | | PUT | `/exercise-progression-graphs/{id}` | Metadaten | | DELETE | `/exercise-progression-graphs/{id}` | Graph + Kanten | | GET | `/exercise-progression-graphs/{id}/edges` | Kantenliste | | POST | `/exercise-progression-graphs/{id}/edges` | Einzelkante | | POST | `/exercise-progression-graphs/{id}/edges/sequence` | Reihe (`steps`) in einer Transaktion | | PUT | `/exercise-progression-graphs/{id}/edges/{edge_id}` | z. B. Notiz | | DELETE | `/exercise-progression-graphs/{id}/edges/{edge_id}` | Kante löschen | | POST | `/exercise-progression-graphs/{id}/edges/delete-batch` | `{ edge_ids }` | Vollständige Pfadtabelle, Auth und Feldgrenzen: **`TRAINING_FRAMEWORK_SPEC.md`** §3. --- ## Exercises ### `GET /exercises` **Query Parameters (Auswahl):** | Parameter | Beschreibung | |-----------|----------------| | `focus_area_ids[]`, `focus_area` | Fokusbereiche (ODER über Liste oder Legacy Einzel-ID) | | `visibility_any[]`, `visibility` | Sichtbarkeit(en) | | `status_any[]`, `status` | Status | | `skill_ids[]`, `skill_id` | Fähigkeit(en) | | `skill_min_level`, `skill_max_level` | Stufe 1–5 auf Übung↔Skill-Zuordnung | | `style_direction_ids[]`, `style_direction_id` | Stilrichtung(en) | | `training_type_ids[]`, `training_type_id` | Trainingsstil(e) | | `target_group_ids[]`, `target_group_id` | Zielgruppe(n) | | `search`, `ai_search` | Volltext (aktuell gleiche Logik; `ai_search` Platzhalter für spätere KI-Suche) | | `include_variants` | `true`: jedes Listenelement enthält optional kompaktes **`variants`** für UI/Planung | | `limit` | Default 50, max 100 | | `offset` | Default 0 | **Response:** `200 OK` Lightweight-Liste; bei `include_variants=true` zusätzlich z. B.: ```json { "id": 1, "title": "Maai - Distanzübung", "variants": [ { "id": 10, "variant_name": "Basis", "sequence_order": 1 } ] } ``` **Errors:** - `401` - Unauthorized - `403` - Forbidden --- ### `GET /exercises/{id}` **Path Parameters:** - `id` (int, required) **Response:** `200 OK` ```json { "id": 1, "title": "Maai - Distanzübung", "summary": "Kurzbeschreibung...", "goal": "Distanzgefühl entwickeln durch Partnerübungen...", "execution": "1. Partnerwahl\n2. Ausgangsstellung Zenkutsu Dachi...", "preparation": "Matten auslegen, Pratzen bereitlegen", "trainer_notes": "Auf korrekten Abstand achten!", "equipment": ["Matten", "Pratzen"], "duration_min": 15, "duration_max": 20, "group_size_min": 8, "group_size_max": 12, "focus_areas": [ { "id": 1, "focus_area_id": 1, "name": "Karate", "abbreviation": "KAR", "color": "#E63946", "icon": "🥋", "is_primary": true } ], "training_styles": [ { "id": 1, "training_style_id": 2, "name": "Shotokan", "abbreviation": "SKA", "is_primary": true }, { "id": 2, "training_style_id": 3, "name": "Goju-Ryu", "abbreviation": "GJR", "is_primary": false } ], "target_groups": [ { "id": 1, "target_group_id": 5, "name": "Breitensportler", "description": "Karate für Freizeit und Fitness", "is_primary": true } ], "age_groups": ["Kinder", "Teenager"], "skills": [ { "id": 1, "skill_id": 10, "skill_name": "Distanzgefühl", "skill_category": "Kumite", "is_primary": true, "intensity": "hoch", "required_level": "grundlagen", "target_level": "aufbau", "ai_suggested": false } ], "variants": [ { "id": 1, "variant_name": "Ohne Partner", "description": "Solo-Variante mit Schattenboxen", "execution_changes": "Stelle dir einen imaginären Partner vor...", "duration_min": 10, "duration_max": 15, "equipment_changes": [], "difficulty_adjustment": "easier", "progression_level": 1, "sequence_order": 1, "prerequisite_variant_id": null }, { "id": 2, "variant_name": "Mit Pratzen", "description": "Fortgeschrittene Variante mit Pratzen-Training", "execution_changes": "Partner hält Pratzen, Ausführung wie Haupt...", "duration_min": 15, "duration_max": 25, "equipment_changes": ["+ Pratzen"], "difficulty_adjustment": "harder", "progression_level": 3, "sequence_order": 3, "prerequisite_variant_id": 1 } ], "media": [ { "id": 1, "media_type": "video", "file_path": "/media/exercises/a1b2c3d4_demo.mp4", "file_size": 5242880, "mime_type": "video/mp4", "original_filename": "demo.mp4", "embed_url": null, "embed_platform": null, "title": "Durchführung Demo", "description": "Zeigt korrekten Abstand", "sort_order": 1, "is_primary": true, "context": "ablauf" }, { "id": 2, "media_type": "video", "file_path": null, "file_size": null, "mime_type": null, "original_filename": null, "embed_url": "https://www.youtube.com/watch?v=abc123", "embed_platform": "youtube", "title": "Erweiterte Techniken", "description": "YouTube-Tutorial von Sensei XY", "sort_order": 2, "is_primary": false, "context": "detail" } ], "visibility": "club", "status": "approved", "created_by": 1, "creator_name": "Lars", "club_id": 1, "club_name": "Dojo Berlin", "import_source": null, "import_id": null, "created_at": "2026-04-20T10:00:00Z", "updated_at": "2026-04-22T14:30:00Z" } ``` **Errors:** - `401` - Unauthorized - `403` - Forbidden (private + not owner) - `404` - Not found --- ### `POST /exercises` **Request Body:** ```json { "title": "Neue Übung", "summary": "Kurzbeschreibung...", "goal": "Ziel der Übung...", "execution": "Durchführung Schritt für Schritt...", "preparation": "Aufbau und Vorbereitung...", "trainer_notes": "Hinweise für Trainer...", "equipment": ["Matten", "Pratzen"], "duration_min": 15, "duration_max": 20, "group_size_min": 8, "group_size_max": 12, "focus_areas_multi": [ {"focus_area_id": 1, "is_primary": true}, {"focus_area_id": 2, "is_primary": false} ], "training_styles_multi": [ {"training_style_id": 2, "is_primary": true} ], "target_groups_multi": [ {"target_group_id": 5, "is_primary": true} ], "age_groups": ["Kinder", "Teenager"], "skills": [ { "skill_id": 10, "is_primary": true, "intensity": "hoch", "required_level": "grundlagen", "target_level": "aufbau" } ], "visibility": "private", "status": "draft", "club_id": 1 } ``` **Required Fields:** - `title` (3-300 chars) - `goal` (10-5000 chars) - `execution` (10-10000 chars) **Response:** `201 Created` (full exercise object wie GET) **Errors:** - `400` - Bad Request (validation error) - `401` - Unauthorized - `403` - Forbidden --- ### `PUT /exercises/{id}` **Request Body:** Same as POST (all fields optional except id) **Inline-Hinweis:** Rich-Text-Felder (`summary`, `goal`, `execution`, `preparation`, `trainer_notes` sowie `execution_changes` bei Varianten) unterstützen Platzhalter `{{exerciseMedia:id}}`, die serverseitig auf kanonisches Span-Markup normalisiert werden. Beim erstmaligen Anlegen ohne vorhandene `exercise_media` wird dies mit 400 abgelehnt. **Response:** `200 OK` (full exercise object) **Errors:** - `400` - Bad Request - `401` - Unauthorized - `403` - Forbidden (not owner) - `404` - Not found --- ### `DELETE /exercises/{id}` **Response:** `200 OK` ```json {"ok": true} ``` **Errors:** - `401` - Unauthorized - `403` - Forbidden (not owner or admin) - `404` - Not found - `409` - Conflict (used in training units) --- ## Variants ### `POST /exercises/{id}/variants` **Request Body:** ```json { "variant_name": "Mit Pratzen", "description": "Fortgeschrittene Variante...", "execution_changes": "Statt freier Distanz mit Pratzen...", "duration_min": 15, "duration_max": 25, "equipment_changes": ["+ Pratzen"], "difficulty_adjustment": "harder", "progression_level": 3, "sequence_order": 3, "prerequisite_variant_id": 1 } ``` **Required Fields:** - `variant_name` (3-200 chars) **Response:** `201 Created` ```json { "id": 2, "exercise_id": 1, "variant_name": "Mit Pratzen", "description": "...", "progression_level": 3, "sequence_order": 3, "prerequisite_variant_id": 1, "created_at": "2026-04-24T10:00:00Z" } ``` **Errors:** - `400` - Bad Request - `401` - Unauthorized - `403` - Forbidden (not owner) - `404` - Exercise not found - `409` - Conflict (prerequisite_variant_id nicht zur gleichen Übung) --- ### `PUT /exercises/{id}/variants/{variant_id}` **Request Body:** Same as POST **Response:** `200 OK` (variant object) --- ### `DELETE /exercises/{id}/variants/{variant_id}` **Response:** `200 OK` ```json {"ok": true} ``` **Errors:** - `409` - Conflict (andere Varianten referenzieren diese als Prerequisite) --- ### `PUT /exercises/{id}/variants/reorder` **Request Body:** ```json { "variant_ids": [3, 1, 2] } ``` **Response:** `200 OK` ```json {"ok": true, "reordered": 3} ``` **Errors:** - `400` - variant_ids nicht vollständig oder falsche Exercise --- ## Media ### `POST /exercises/{id}/media` **Content-Type:** `multipart/form-data` **Form Fields:** - `file` (File, optional) - Image/Video file - `embed_url` (string, optional) - YouTube/Instagram/Vimeo URL - `media_type` (enum, required) - `image | video | document | sketch` - `title` (string, optional) - `description` (string, optional) - `context` (enum, optional, legacy UI-Feld) - `ablauf | detail | trainer_hint` **Entweder `file` ODER `embed_url` (nicht beides).** **Response:** `201 Created` ```json { "id": 5, "exercise_id": 1, "media_type": "video", "file_path": "/media/exercises/a1b2c3d4_demo.mp4", "file_size": 5242880, "mime_type": "video/mp4", "original_filename": "demo.mp4", "embed_url": null, "embed_platform": null, "title": "Demo", "description": null, "sort_order": 3, "is_primary": false, "context": "ablauf", "created_at": "2026-04-24T10:00:00Z" } ``` **Errors:** - `400` - Bad Request (invalid file type, missing file/embed_url) - `401` - Unauthorized - `403` - Forbidden - `413` - File too large (> 50MB) **Supported Formats:** - **Images:** `image/jpeg`, `image/png`, `image/gif` - **Videos:** `video/mp4` - **Documents:** `application/pdf` - **Embeds:** `youtube.com`, `youtu.be`, `instagram.com`, `vimeo.com` --- ### `PUT /exercises/{id}/media/{media_id}` **Request Body:** ```json { "title": "Neuer Titel", "description": "Neue Beschreibung", "is_primary": true } ``` `context` bleibt backendseitig kompatibel, wird in aktuellen Bearbeitungsflüssen jedoch nicht mehr aktiv zur UI-Zuordnung verwendet (Inline-Platzhalter priorisiert). **Response:** `200 OK` (media object) --- ### `DELETE /exercises/{id}/media/{media_id}` **Response:** `200 OK` ```json {"ok": true} ``` **Deletes:** DB-Eintrag + Datei (wenn `file_path` gesetzt) --- ### `PUT /exercises/{id}/media/reorder` **Request Body:** ```json { "media_ids": [2, 1, 3] } ``` **Response:** `200 OK` ```json {"ok": true, "reordered": 3} ``` --- ## KI-Assistenz ### `POST /exercises/ai/suggest` Generiert KI-Vorschläge für eine **noch nicht gespeicherte** Übung. Wird beim Klick auf „✨ KI-Vorschlag" im Formular aufgerufen. **Request Body:** ```json { "title": "Maai - Distanzübung", "goal": "Distanzgefühl entwickeln...", "execution": "1. Partnerwahl\n2. Ausgangsstellung..." } ``` **Mindestanforderung:** `goal` oder `execution` muss vorhanden sein (min. 50 Zeichen) **Response:** `200 OK` ```json { "summary": { "text": "Partnerübung zur Entwicklung des Distanzgefühls. Trainiert räumliche Wahrnehmung und reaktives Verhalten.", "ai_generated": true, "model": "anthropic/claude-sonnet-4" }, "skills": [ { "skill_id": 10, "skill_name": "Distanzgefühl", "skill_category": "Kumite", "required_level": "grundlagen", "target_level": "aufbau", "intensity": "hoch", "is_primary": true, "confidence": 0.92 }, { "skill_id": 15, "skill_name": "Reaktionsschnelligkeit", "skill_category": "Athletik", "required_level": "einsteiger", "target_level": "grundlagen", "intensity": "mittel", "is_primary": false, "confidence": 0.74 } ] } ``` **Errors:** - `400` - Zu wenig Text (< 50 Zeichen) - `503` - KI nicht verfügbar (OPENROUTER_API_KEY nicht konfiguriert) --- ### `POST /exercises/{id}/ai/regenerate` Generiert Vorschläge für eine **bestehende** Übung neu. **Request Body:** ```json { "regenerate": ["summary", "skills"] } ``` **Response:** `200 OK` (gleiche Struktur wie `/ai/suggest`) **Hinweis:** Gibt nur Vorschläge zurück – nichts wird automatisch gespeichert. Trainer muss im Frontend aktiv übernehmen. --- ## Permissions ### Sichtbarkeits-Workflow | Von → Nach | Wer darf das? | |-----------|---------------| | `draft → in_review` | Ersteller, Club-Admin | | `in_review → approved` | Club-Admin, Super-Admin | | `approved → archived` | Club-Admin, Super-Admin | | `* → draft` | Super-Admin | ### Sichtbarkeit (`visibility`) | Änderung | Wer darf das? | |----------|---------------| | `private → club` | Ersteller, Club-Admin | | `club → official` | Club-Admin, Super-Admin | | `official → club` | Super-Admin | ### Owner-Checks - **Bearbeiten** (PUT): Nur Ersteller oder Club-Admin - **Löschen** (DELETE): Nur Ersteller oder Super-Admin - **Lesen** (`private`): Nur Ersteller **403 Fehler-Beispiel:** ```json {"detail": "Keine Berechtigung. Nur der Ersteller oder ein Admin kann diese Übung bearbeiten."} ``` --- ## Exercise Blocks ### `GET /exercise-blocks` **Query Parameters:** - `is_template` (bool, optional) - nur Templates oder nur reguläre Blocks - `visibility` (enum, optional) - `private | club | official` - `club_id` (int, optional) - Filter nach Verein - `search` (string, optional) - Suche in name - `limit` (int, optional, default: 50) - `offset` (int, optional, default: 0) **Response:** `200 OK` ```json [ { "id": 1, "name": "Aufwärmblock Kumite", "description": "Standard-Aufwärmprogramm für Kumite", "is_template": false, "item_count": 4, "club_id": 1, "club_name": "Dojo Berlin", "created_by": 1, "creator_name": "Lars", "visibility": "club", "created_at": "2026-04-20T10:00:00Z" } ] ``` --- ### `GET /exercise-blocks/{id}` **Response:** `200 OK` ```json { "id": 1, "name": "Aufwärmblock Kumite", "description": "Standard-Aufwärmprogramm für Kumite", "goal": "Sportler auf Kumite-Training vorbereiten", "is_template": false, "club_id": 1, "club_name": "Dojo Berlin", "created_by": 1, "creator_name": "Lars", "visibility": "club", "items": [ { "id": 1, "sequence_order": 1, "is_placeholder": false, "exercise_id": 5, "exercise_title": "Maai - Distanzübung", "exercise_summary": "Distanzgefühl entwickeln...", "variant_id": null, "variant_name": null, "notes": "Leicht anfangen, nur 5min", "placeholder_criteria": null, "placeholder_label": null }, { "id": 2, "sequence_order": 2, "is_placeholder": true, "exercise_id": null, "exercise_title": null, "variant_id": null, "notes": "Hier eine Schlag-Übung einsetzen", "placeholder_criteria": {"focus_area_id": 1, "max_duration": 10}, "placeholder_label": "Schlag-Übung (max. 10 min)" } ], "created_at": "2026-04-20T10:00:00Z", "updated_at": "2026-04-22T14:30:00Z" } ``` **Errors:** - `403` - Forbidden (private Block, nicht Ersteller) - `404` - Not found --- ### `POST /exercise-blocks` **Request Body:** ```json { "name": "Aufwärmblock Kumite", "description": "Standard-Aufwärmprogramm", "goal": "Sportler vorbereiten", "is_template": false, "visibility": "private", "club_id": 1 } ``` **Required Fields:** - `name` (3-200 chars) **Response:** `201 Created` (full block object wie GET) --- ### `PUT /exercise-blocks/{id}` **Request Body:** Same as POST (all fields optional) **Response:** `200 OK` (full block object) **Errors:** - `403` - Forbidden (nicht Ersteller/Admin) - `404` - Not found --- ### `DELETE /exercise-blocks/{id}` **Response:** `200 OK` ```json {"ok": true} ``` **Errors:** - `403` - Forbidden - `404` - Not found - `409` - Conflict (Block in Trainingsplan verwendet) --- ### `POST /exercise-blocks/{id}/items` **Request Body (konkretes Exercise):** ```json { "exercise_id": 5, "variant_id": null, "sequence_order": 3, "notes": "Leicht anfangen" } ``` **Request Body (Platzhalter):** ```json { "is_placeholder": true, "placeholder_label": "Schlag-Übung (max. 10 min)", "placeholder_criteria": { "focus_area_id": 1, "max_duration": 10 }, "sequence_order": 4, "notes": "Variabel je nach Gruppe" } ``` **Response:** `201 Created` ```json { "id": 5, "block_id": 1, "sequence_order": 3, "is_placeholder": false, "exercise_id": 5, "exercise_title": "Maai - Distanzübung", "variant_id": null, "notes": "Leicht anfangen", "created_at": "2026-04-24T10:00:00Z" } ``` **Errors:** - `400` - Bad Request (weder exercise_id noch is_placeholder=true) - `404` - Block oder Exercise nicht gefunden - `409` - Conflict (sequence_order bereits vergeben) --- ### `PUT /exercise-blocks/{id}/items/{item_id}` **Request Body:** Gleiche Felder wie POST, alle optional **Response:** `200 OK` (item object) --- ### `DELETE /exercise-blocks/{id}/items/{item_id}` **Response:** `200 OK` ```json {"ok": true} ``` --- ### `PUT /exercise-blocks/{id}/items/reorder` **Request Body:** ```json { "item_ids": [3, 1, 2, 4] } ``` **Response:** `200 OK` ```json { "ok": true, "reordered": 4, "items": [ {"id": 3, "sequence_order": 1}, {"id": 1, "sequence_order": 2}, {"id": 2, "sequence_order": 3}, {"id": 4, "sequence_order": 4} ] } ``` **Errors:** - `400` - item_ids unvollständig oder gehören nicht zu diesem Block --- ## Validation Rules ### Exercise - `title`: 3-300 chars, unique per club - `goal`: 10-5000 chars - `execution`: 10-10000 chars - `duration_min/max`: 0-480 (8 hours) - `group_size_min/max`: 1-100 - `visibility`: enum (private, club, official) - `status`: enum (draft, in_review, approved, archived) ### Variant - `variant_name`: 3-200 chars - `progression_level`: 1-10 - `prerequisite_variant_id`: must exist, must be same exercise ### Media - File size: max 50MB - Mime types: `image/jpeg, image/png, image/gif, video/mp4, application/pdf` - Embed platforms: `youtube, instagram, vimeo` - Inline-Rich-Text: `{{exerciseMedia:id}}` bzw. `data-shinkan-exercise-media=""`; optional `data-shinkan-exercise-media-size="small|medium|full"` für Layout. ### Exercise Block - `name`: 3-200 chars ### Exercise Skills - `required_level`: enum – `einsteiger | grundlagen | aufbau | fortgeschritten | experte` (optional/nullable) - `target_level`: enum – gleiche Werte (optional/nullable) - `intensity`: enum – `niedrig | mittel | hoch` (optional/nullable) - `target_level` sollte >= `required_level` sein (Warnung, kein Fehler) ### Exercise Block Item - `sequence_order`: muss unique pro Block sein - `exercise_id`: muss existieren und zugänglich sein (visibility check) - `is_placeholder = true`: `exercise_id` muss NULL sein - `placeholder_criteria`: bekannte Keys nur (focus_area_id, training_style_id, target_group_id, skill_ids, max_duration, min_duration, difficulty, visibility) --- ## Error Response Format **Standard:** ```json { "detail": "Human-readable error message" } ``` **Validation (detailliert):** ```json { "detail": "Validation failed", "errors": [ {"field": "title", "message": "Titel muss mindestens 3 Zeichen lang sein"}, {"field": "goal", "message": "Ziel ist ein Pflichtfeld"} ] } ``` --- ## HTTP Status Codes - `200 OK` - Erfolgreiche GET/PUT/DELETE - `201 Created` - Erfolgreiche POST - `400 Bad Request` - Validierung fehlgeschlagen - `401 Unauthorized` - Kein/ungültiges Token - `403 Forbidden` - Keine Berechtigung - `404 Not Found` - Ressource nicht gefunden - `409 Conflict` - Duplikat, Constraint - `413 Payload Too Large` - Datei zu groß - `500 Internal Server Error` - Server-Fehler --- **Version:** 1.5 **Letzte Änderung:** 2026-05-08 **Status:** Living Spec