{ "openapi": "3.0.3", "info": { "title": "Tri-Hub Ernährungsberatung & Booking API", "description": "API-Spezifikation für die Angebotsübersicht (US2.1), die Paket-Detailansicht und den Buchungsservice (US2.4 Paket buchen).", "version": "1.1.0" }, "servers": [ { "url": "http://localhost:3000/api", "description": "Lokaler Node.js Entwicklungsserver" } ], "paths": { "/packages": { "get": { "summary": "Alle Ernährungs-Pakete abrufen (Kurzübersicht)", "description": "Liefert die Basisdaten aus packages.js für die Angebotsübersicht (US2.1).", "tags": ["Packages"], "responses": { "200": { "description": "Erfolgreiche Rückgabe der Paketliste", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/PackageOverview" } } } } } } } }, "/packages/{id}": { "get": { "summary": "Detaillierte Paketinformationen für Detailseite & Bestellprozess abrufen", "description": "Liefert die ausführlichen Paketdaten aus packagesdetails.js (inkl. Preis, Laufzeit, Leistungen und Varianten), um sie auf der Detailseite und in der Bestellzusammenfassung des Booking-Service anzuzeigen.", "tags": ["Packages", "Booking"], "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Eindeutige ID des Pakets (z. B. ernaehrung-starter)", "schema": { "type": "string", "example": "ernaehrung-standard" } } ], "responses": { "200": { "description": "Detaillierte Paketdaten erfolgreich geladen", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PackageDetail" } } } }, "404": { "description": "Paket mit dieser ID wurde nicht gefunden", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/bookings/preview": { "post": { "summary": "Preis- und Leistungsübersicht für den Bestellprozess berechnen", "description": "Berechnet die Bestellübersicht aus dem serverseitigen Paketkatalog. Preisangaben aus der Anfrage werden ignoriert. Ohne Varianten-ID wird bei Paketen mit Varianten die erste Variante gewählt. Es wird keine Buchung angelegt.", "tags": ["Booking"], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BookingPreviewRequest" } } } }, "responses": { "200": { "description": "Berechnete Bestellübersicht für das Checkout-Formular", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BookingPreviewResponse" } } } }, "400": { "description": "Ungültige Paket- oder Varianten-ID", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/bookings": { "post": { "summary": "Paket verbindlich buchen (US2.4)", "description": "Speichert Paket, gewählte Variante, Leistungen und Preise zum Buchungszeitpunkt und startet den Bestätigungsversand. Der aktuelle Ablauf simuliert die Bestellung ohne Zahlung.", "tags": ["Booking"], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BookingCreateRequest" } } } }, "responses": { "201": { "description": "Buchung erfolgreich angelegt (Bestellbestätigung)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BookingConfirmation" } } } }, "400": { "description": "Fehlende oder ungültige Eingabedaten", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "200": { "description": "Bereits gespeicherte Buchung; keine zweite Buchung oder Mail", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BookingConfirmation" } } } }, "202": { "description": "Buchung gespeichert; Versand ausstehend, fehlgeschlagen oder unklar", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BookingConfirmation" } } } }, "409": { "description": "Anfrageschlüssel wurde mit anderen Buchungsdaten verwendet", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "413": { "description": "Anfrage größer als 8 KiB", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "415": { "description": "JSON-Content-Type erforderlich", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "503": { "description": "Speicherung oder Statusprüfung nicht verfügbar", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "UUID v4; bei Wiederholung denselben Schlüssel und dieselben Buchungsdaten verwenden." } ] } } }, "components": { "schemas": { "PackageOverview": { "type": "object", "required": [ "id", "type", "title", "description", "fokus", "anamnese", "begleitung", "buttonText" ], "properties": { "id": { "type": "string", "example": "ernaehrung-standard" }, "type": { "type": "string", "example": "STANDARD" }, "title": { "type": "string", "example": "Für Best Ager mit ersten Erfahrungen und klaren Zielen" }, "description": { "type": "string" }, "fokus": { "type": "string" }, "anamnese": { "type": "string" }, "begleitung": { "type": "string" }, "buttonText": { "type": "string", "example": "Standard-Paket ansehen" }, "highlight": { "type": "string", "nullable": true, "example": "BESONDERS PASSEND" } } }, "PackageVariant": { "type": "object", "properties": { "variantId": { "type": "string", "example": "ernaehrung-premium-24" }, "label": { "type": "string", "example": "24-Wochen-Variante" }, "durationWeeks": { "type": "integer", "example": 24 }, "price": { "type": "number", "format": "float", "example": 799.0, "description": "Bruttopreis der Variante inklusive MwSt." } } }, "PackageDetail": { "type": "object", "required": [ "id", "type", "title", "subtitle", "durationWeeks", "price", "currency", "taxRate", "includedFeatures" ], "properties": { "id": { "type": "string", "example": "ernaehrung-standard" }, "type": { "type": "string", "example": "STANDARD" }, "badge": { "type": "string", "example": "BESONDERS PASSEND" }, "title": { "type": "string", "example": "Für Best Ager mit ersten Erfahrungen und klaren Zielen" }, "subtitle": { "type": "string" }, "durationWeeks": { "type": "integer", "example": 24 }, "durationLabel": { "type": "string", "example": "24 Wochen Begleitung" }, "price": { "type": "number", "format": "float", "example": 449.0, "description": "Bruttopreis inklusive der angegebenen MwSt." }, "currency": { "type": "string", "example": "EUR" }, "billingInterval": { "type": "string", "example": "einmalig" }, "taxRate": { "type": "integer", "example": 19 }, "summaryForBooking": { "type": "string", "example": "Standard-Paket Sporternährung (24 Wochen Coaching)" }, "targetGroup": { "type": "array", "items": { "type": "string" } }, "includedFeatures": { "type": "array", "items": { "type": "string" } }, "processSteps": { "type": "array", "items": { "type": "object", "properties": { "step": { "type": "string" }, "text": { "type": "string" } } } }, "variants": { "type": "array", "nullable": true, "items": { "$ref": "#/components/schemas/PackageVariant" } }, "ctaButtonText": { "type": "string", "example": "Standard-Paket jetzt buchen" } } }, "BookingPreviewRequest": { "type": "object", "required": ["packageId"], "properties": { "packageId": { "type": "string", "example": "ernaehrung-premium" }, "variantId": { "type": "string", "nullable": true, "example": "ernaehrung-premium-52" } } }, "BookingPreviewResponse": { "type": "object", "properties": { "packageId": { "type": "string", "example": "ernaehrung-premium" }, "variantId": { "type": "string", "example": "ernaehrung-premium-52", "nullable": true }, "title": { "type": "string", "example": "Für maximale Individualität und intensive 1:1-Begleitung" }, "summaryForBooking": { "type": "string" }, "durationWeeks": { "type": "integer", "example": 52 }, "netPrice": { "type": "number", "format": "float", "example": 1175.63 }, "taxAmount": { "type": "number", "format": "float", "example": 223.37 }, "grossPrice": { "type": "number", "format": "float", "example": 1399.0 }, "currency": { "type": "string", "example": "EUR" }, "variantLabel": { "type": "string", "nullable": true }, "type": { "type": "string" }, "subtitle": { "type": "string" }, "durationLabel": { "type": "string" }, "quantity": { "type": "integer", "enum": [1] }, "billingInterval": { "type": "string", "example": "einmalig" }, "taxRate": { "type": "number", "example": 19 }, "includedFeatures": { "type": "array", "items": { "type": "string" } }, "processSteps": { "type": "array", "items": { "type": "object", "properties": { "step": { "type": "string" }, "text": { "type": "string" } } } } }, "required": [ "packageId", "variantId", "title", "summaryForBooking", "durationWeeks", "netPrice", "taxAmount", "grossPrice", "currency", "variantLabel", "type", "subtitle", "durationLabel", "quantity", "billingInterval", "taxRate", "includedFeatures", "processSteps" ] }, "BookingCreateRequest": { "type": "object", "required": ["packageId"], "properties": { "packageId": { "type": "string", "example": "ernaehrung-standard" }, "variantId": { "type": "string", "nullable": true, "example": "ernaehrung-premium-52" }, "name": { "type": "string", "minLength": 1, "maxLength": 120, "example": "Thomas Müller" }, "email": { "type": "string", "format": "email", "maxLength": 254, "example": "thomas.mueller@example.de" }, "phone": { "type": "string", "maxLength": 50, "description": "Optionale Telefonnummer bei Übergabe von name und email, ohne Steuerzeichen. Bei customer gilt customer.phone.", "example": "+49 170 1234567" }, "customer": { "type": "object", "required": ["firstName", "lastName", "email"], "properties": { "firstName": { "type": "string", "example": "Thomas" }, "lastName": { "type": "string", "example": "Müller" }, "email": { "type": "string", "format": "email", "example": "thomas.mueller@example.de" }, "phone": { "type": "string", "example": "+49 170 1234567" }, "ageGroup": { "type": "string", "example": "50-59" }, "notes": { "type": "string", "example": "Vorbereitung auf meine erste Mitteldistanz im August." } } }, "acceptedTerms": { "type": "boolean", "example": true, "enum": [true] } }, "anyOf": [ { "required": ["customer", "acceptedTerms"] }, { "required": ["name", "email"] } ], "description": "Kundendaten gemäß customer mit acceptedTerms=true. Bestehende Clients können alternativ name und email sowie optional phone übergeben." }, "BookingConfirmation": { "type": "object", "properties": { "bookingId": { "type": "string", "example": "TH-000001" }, "status": { "type": "string", "example": "CONFIRMED" }, "createdAt": { "type": "string", "format": "date-time" }, "bookedPackage": { "allOf": [ { "$ref": "#/components/schemas/BookingPreviewResponse" } ], "nullable": true, "description": "Gespeicherter Bestellstand; bei älteren Buchungen ohne diese Daten null." }, "customerEmail": { "type": "string", "example": "thomas.mueller@example.de" }, "packageId": { "type": "string" }, "packageName": { "type": "string" }, "saved": { "type": "boolean" }, "emailStatus": { "type": "string", "enum": ["pending", "accepted", "failed", "unknown"], "description": "accepted bedeutet SMTP-Annahme, keine bestätigte Zustellung." }, "replayed": { "type": "boolean" } } }, "ErrorResponse": { "type": "object", "required": ["error"], "properties": { "error": { "type": "object", "required": ["code", "message"], "properties": { "code": { "type": "string", "example": "INVALID_VARIANT" }, "message": { "type": "string" }, "fields": { "type": "object", "additionalProperties": { "type": "string" } } } } } } } } }