{"openapi":"3.1.0","info":{"title":"MealPlan API","description":"REST API for the MealPlan web application","contact":{"name":"Fynn Koch","email":"mail@fynn-koch.de"},"version":"0.0.1-SNAPSHOT"},"servers":[{"url":"/"}],"security":[{"Bearer Authentication":[]}],"tags":[{"name":"Authentication","description":"Registration and login"},{"name":"Foods","description":"Central ingredient catalogue (scoped to the authenticated user)"},{"name":"Plans","description":"Meal plan management (scoped to the authenticated user)"},{"name":"Invites","description":"Admin invite management"},{"name":"Health","description":"Service liveness probe"},{"name":"Invite Requests","description":"Public access requests"}],"paths":{"/api/plans/{id}":{"get":{"tags":["Plans"],"summary":"Get plan","description":"Returns a single plan with its full day/meal/ingredient tree.","operationId":"getPlanById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanResponse"}}}}}},"put":{"tags":["Plans"],"summary":"Update plan","description":"Replaces a plan and its full tree with the provided aggregate.","operationId":"updatePlan","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanResponse"}}}}}},"delete":{"tags":["Plans"],"summary":"Delete plan","description":"Deletes a plan and its full tree.","operationId":"deletePlan","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK"}}}},"/api/foods/{id}":{"get":{"tags":["Foods"],"summary":"Get food","operationId":"getFoodById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodResponse"}}}}}},"put":{"tags":["Foods"],"summary":"Update food","operationId":"updateFood","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodResponse"}}}}}},"delete":{"tags":["Foods"],"summary":"Delete food","description":"Deletes a catalogue food. Fails if it is still used in a plan.","operationId":"deleteFood","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK"}}}},"/api/plans":{"get":{"tags":["Plans"],"summary":"List plans","description":"Returns the authenticated user's plans (summary), newest first.","operationId":"getPlans","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PlanSummaryResponse"}}}}}}},"post":{"tags":["Plans"],"summary":"Create plan","description":"Creates a plan as an aggregate (days, meals, ingredients).","operationId":"createPlan","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanResponse"}}}}}}},"/api/invites/{id}/decline":{"post":{"tags":["Invites"],"summary":"Decline invite","description":"Declines a requested invite (admin only).","operationId":"declineInvite","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteResponse"}}}}}}},"/api/invites/{id}/approve":{"post":{"tags":["Invites"],"summary":"Approve invite","description":"Approves a requested invite, generates a token and emails the registration link to the requester (admin only).","operationId":"approveInvite","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteResponse"}}}}}}},"/api/invite-requests":{"post":{"tags":["Invite Requests"],"summary":"Request access","description":"A visitor requests access by email. The site admin is notified and can approve or decline. Always returns 202 to avoid leaking account existence.","operationId":"requestInvite","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteRequestRequest"}}},"required":true},"responses":{"200":{"description":"OK"}},"security":[]}},"/api/foods":{"get":{"tags":["Foods"],"summary":"List foods","description":"Returns the authenticated user's catalogue foods, ordered by name.","operationId":"getFoods","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FoodResponse"}}}}}}},"post":{"tags":["Foods"],"summary":"Create food","description":"Creates a catalogue food with macros for a reference quantity.","operationId":"createFood","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodResponse"}}}}}}},"/api/auth/register":{"post":{"tags":["Authentication"],"summary":"Register","description":"Completes registration with an approved invite token and returns a JWT.","operationId":"register","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}}},"security":[]}},"/api/auth/login":{"post":{"tags":["Authentication"],"summary":"Login","description":"Authenticate with email and password to receive a JWT.","operationId":"login","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}}},"security":[]}},"/api/invites":{"get":{"tags":["Invites"],"summary":"List invites","description":"Returns all invites, newest first (admin only).","operationId":"getInvites","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InviteResponse"}}}}}}}},"/api/health":{"get":{"tags":["Health"],"summary":"Health check","description":"Returns the service status","operationId":"health","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}},"security":[]}}},"components":{"schemas":{"IngredientRequest":{"type":"object","properties":{"foodId":{"type":"string","format":"uuid"},"quantity":{"type":"number"},"sortOrder":{"type":"integer","format":"int32"}},"required":["foodId","quantity"]},"MealRequest":{"type":"object","properties":{"title":{"type":"string","minLength":1},"recipeNotes":{"type":"string"},"sortOrder":{"type":"integer","format":"int32"},"ingredients":{"type":"array","items":{"$ref":"#/components/schemas/IngredientRequest"}}},"required":["title"]},"PlanDayRequest":{"type":"object","properties":{"dayIndex":{"type":"integer","format":"int32"},"label":{"type":"string"},"meals":{"type":"array","items":{"$ref":"#/components/schemas/MealRequest"}}},"required":["dayIndex"]},"PlanRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1},"description":{"type":"string"},"status":{"type":"string","enum":["DRAFT","ACTIVE","ARCHIVED"]},"days":{"type":"array","items":{"$ref":"#/components/schemas/PlanDayRequest"}}},"required":["name"]},"IngredientResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"foodId":{"type":"string","format":"uuid"},"foodName":{"type":"string"},"unit":{"type":"string","enum":["G","ML","STK"]},"quantity":{"type":"number"},"referenceQuantity":{"type":"number"},"kcal":{"type":"number"},"protein":{"type":"number"},"carbs":{"type":"number"},"fat":{"type":"number"},"sortOrder":{"type":"integer","format":"int32"}},"required":["carbs","fat","foodId","foodName","id","kcal","protein","quantity","referenceQuantity","unit"]},"MealResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"recipeNotes":{"type":"string"},"sortOrder":{"type":"integer","format":"int32"},"ingredients":{"type":"array","items":{"$ref":"#/components/schemas/IngredientResponse"}},"kcal":{"type":"number"},"protein":{"type":"number"},"carbs":{"type":"number"},"fat":{"type":"number"}},"required":["carbs","fat","id","ingredients","kcal","protein","title"]},"PlanDayResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"dayIndex":{"type":"integer","format":"int32"},"label":{"type":"string"},"meals":{"type":"array","items":{"$ref":"#/components/schemas/MealResponse"}},"kcal":{"type":"number"},"protein":{"type":"number"},"carbs":{"type":"number"},"fat":{"type":"number"}},"required":["carbs","fat","id","kcal","meals","protein"]},"PlanResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string"},"durationDays":{"type":"integer","format":"int32"},"status":{"type":"string","enum":["DRAFT","ACTIVE","ARCHIVED"]},"source":{"type":"string","enum":["MANUAL","AI"]},"days":{"type":"array","items":{"$ref":"#/components/schemas/PlanDayResponse"}},"kcal":{"type":"number"},"protein":{"type":"number"},"carbs":{"type":"number"},"fat":{"type":"number"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["carbs","createdAt","days","fat","id","kcal","name","protein","source","status","updatedAt"]},"FoodRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1},"unit":{"type":"string","enum":["G","ML","STK"]},"referenceQuantity":{"type":"number"},"kcal":{"type":"number"},"protein":{"type":"number"},"carbs":{"type":"number"},"fat":{"type":"number"}},"required":["carbs","fat","kcal","name","protein","referenceQuantity","unit"]},"FoodResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"unit":{"type":"string","enum":["G","ML","STK"]},"referenceQuantity":{"type":"number"},"kcal":{"type":"number"},"protein":{"type":"number"},"carbs":{"type":"number"},"fat":{"type":"number"}},"required":["carbs","fat","id","kcal","name","protein","referenceQuantity","unit"]},"InviteResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"status":{"type":"string","enum":["REQUESTED","APPROVED","DECLINED","USED"]},"decidedAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"usedAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["createdAt","email","id","status","updatedAt"]},"InviteRequestRequest":{"type":"object","properties":{"email":{"type":"string","format":"email","minLength":1}},"required":["email"]},"RegisterRequest":{"type":"object","properties":{"token":{"type":"string","minLength":1},"email":{"type":"string","format":"email","minLength":1},"password":{"type":"string","maxLength":2147483647,"minLength":8},"displayName":{"type":"string","minLength":1}},"required":["displayName","email","password","token"]},"LoginResponse":{"type":"object","properties":{"token":{"type":"string"},"email":{"type":"string"},"displayName":{"type":"string"},"role":{"type":"string","enum":["USER","ADMIN"]}},"required":["displayName","email","role","token"]},"LoginRequest":{"type":"object","properties":{"email":{"type":"string","format":"email","minLength":1},"password":{"type":"string","minLength":1}},"required":["email","password"]},"PlanSummaryResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string"},"durationDays":{"type":"integer","format":"int32"},"status":{"type":"string","enum":["DRAFT","ACTIVE","ARCHIVED"]},"source":{"type":"string","enum":["MANUAL","AI"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["createdAt","id","name","source","status","updatedAt"]},"HealthResponse":{"type":"object","properties":{"status":{"type":"string"}},"required":["status"]}},"securitySchemes":{"Bearer Authentication":{"type":"http","description":"Enter your JWT token","name":"Bearer Authentication","scheme":"bearer","bearerFormat":"JWT"}}}}