{"openapi":"3.1.0","info":{"title":"FitFly Engine API","version":"0.2.0","description":"Deterministic training + nutrition engine. Programs, per-exercise progression (double-progression, e1RM, PRs), readiness autoregulation, same-muscle swaps, nutrition targets and USDA-truth-checked meal plans, and an auditable 'what changed & why' changelog. The engine does the math, rules, and safety; your product does the experience. Also available as an MCP server at /mcp (Streamable HTTP) for AI-agent hosts."},"servers":[{"url":"https://{host}/api/engine","variables":{"host":{"default":"localhost:8787"}}}],"security":[{"partnerKey":[],"partnerAthlete":[]},{"bearer":[]}],"components":{"securitySchemes":{"partnerKey":{"type":"apiKey","in":"header","name":"x-fitfly-key","description":"B2B partner API key (ffk_…)."},"partnerAthlete":{"type":"apiKey","in":"header","name":"x-fitfly-athlete","description":"Athlete id the partner is acting on (required with x-fitfly-key on coaching endpoints)."},"bearer":{"type":"http","scheme":"bearer","description":"An fft_ personal access token, or a Supabase user JWT."}},"schemas":{"Athlete":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","nullable":true},"external_ref":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"PartnerSettings":{"type":"object","description":"White-label presentation config. Engine math/safety is not configurable.","properties":{"coachName":{"type":"string","description":"Persona name used in coach-voiced text."},"voice":{"type":"string","description":"Tone words for coach-voiced text."},"units":{"type":"string","enum":["kg","lb"],"description":"Units in human-readable strings (numeric fields stay kg)."}}},"Target":{"type":"object","properties":{"exercise_id":{"type":"string"},"weightKg":{"type":"number","nullable":true,"description":"Working weight (kg). Null until the athlete has logged the lift (day-one calibration)."},"reps":{"type":"integer","nullable":true},"load_guidance":{"type":"string","description":"Human-readable target, e.g. '60kg x 8' or 'RPE 7'. Respects partner units setting."},"warmup":{"type":"array","items":{"type":"object","properties":{"percent":{"type":"number"},"weightKg":{"type":"number"},"reps":{"type":"integer"},"restSec":{"type":"integer"}}}}}},"SessionDay":{"type":"object","properties":{"day_index":{"type":"integer"},"focus":{"type":"string"},"duration_min":{"type":"integer"},"coach_note":{"type":"string"},"blocks":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["warmup","main","cooldown"]},"exercises":{"type":"array","items":{"type":"object","properties":{"exercise_id":{"type":"string"},"sets":{"type":"integer"},"reps":{"type":"string"},"load_guidance":{"type":"string"},"rest_sec":{"type":"integer"},"swap_options":{"type":"array","items":{"type":"string"}}}}}}}}}},"NutritionTargets":{"type":"object","properties":{"targetKcal":{"type":"number"},"proteinG":{"type":"number"},"carbsG":{"type":"number"},"fatG":{"type":"number"},"daySplit":{"type":"object","description":"Training-day / rest-day split."}}},"Error":{"type":"object","properties":{"error":{"type":"string"}}}}},"paths":{"/health":{"get":{"summary":"Health check (no auth)","security":[],"responses":{"200":{"description":"ok"}}}},"/partner/athletes":{"post":{"summary":"Create an athlete under your partner account","description":"The returned athlete id is the handle for every coaching endpoint (send as x-fitfly-athlete).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"externalRef":{"type":"string","description":"Your own id for this athlete."}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"athlete":{"$ref":"#/components/schemas/Athlete"}}}}}}}},"get":{"summary":"List your athletes","responses":{"200":{"description":"Athletes","content":{"application/json":{"schema":{"type":"object","properties":{"athletes":{"type":"array","items":{"$ref":"#/components/schemas/Athlete"}}}}}}}}}},"/partner/athletes/{athleteId}/mcp-token":{"post":{"summary":"Mint an agent token acting as one of your athletes","description":"Returns a revocable fft_ bearer once. Agent usage with it meters to your account.","parameters":[{"name":"athleteId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Token (shown once)"},"404":{"description":"Athlete not yours","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/partner/usage":{"get":{"summary":"Active-athlete usage for a month (what you're billed on)","parameters":[{"name":"month","in":"query","schema":{"type":"string","example":"2026-07-01"},"description":"First of month, UTC. Default: current month."}],"responses":{"200":{"description":"Usage","content":{"application/json":{"schema":{"type":"object","properties":{"month":{"type":"string"},"activeAthletes":{"type":"integer"},"athletes":{"type":"array","items":{"type":"object","properties":{"athlete_id":{"type":"string"},"event_count":{"type":"integer"},"last_seen":{"type":"string"}}}}}}}}}}}},"/partner/settings":{"get":{"summary":"Get your white-label settings","responses":{"200":{"description":"Settings","content":{"application/json":{"schema":{"type":"object","properties":{"settings":{"$ref":"#/components/schemas/PartnerSettings"}}}}}}}},"patch":{"summary":"Update white-label settings (merge; null clears a key)","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerSettings"}}}},"responses":{"200":{"description":"Updated settings"},"400":{"description":"Unknown key / bad value","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/profile":{"put":{"summary":"Set the athlete's body + training profile","description":"Body drives nutrition targets; training profile unlocks program generation.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"object","properties":{"weightKg":{"type":"number"},"heightCm":{"type":"number"},"age":{"type":"integer"},"gender":{"type":"string"},"activityLevel":{"type":"string","enum":["sedentary","low_active","active","very_active"]},"goal":{"type":"string","enum":["cut","maintain","lean_bulk"]}}},"training":{"type":"object","properties":{"experience":{"type":"string","enum":["beginner","intermediate","advanced"]},"trainingGoal":{"type":"string","enum":["strength","hypertrophy","fat_loss","general_fitness"]},"daysPerWeek":{"type":"integer","minimum":2,"maximum":6},"minutesPerSession":{"type":"integer"},"equipment":{"type":"array","items":{"type":"string"}},"injuryFlags":{"type":"array","items":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"Stored; returns recomputed nutrition targets"}}}},"/program/regenerate":{"post":{"summary":"Generate/rebuild the athlete's program (~20s AI call)","responses":{"200":{"description":"programId + week"},"400":{"description":"No training profile yet"}}}},"/program/current":{"get":{"summary":"The active program week","responses":{"200":{"description":"Program"},"404":{"description":"None generated yet"}}}},"/session/today":{"get":{"summary":"Today's session: day + concrete targets + nutrition + cycle","description":"Auto-applies today's readiness check-in (autoregulation) and any today-scoped swap.","parameters":[{"name":"dayIndex","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"Session","content":{"application/json":{"schema":{"type":"object","properties":{"day":{"$ref":"#/components/schemas/SessionDay"},"targets":{"type":"array","items":{"$ref":"#/components/schemas/Target"}},"nutrition":{"$ref":"#/components/schemas/NutritionTargets"},"adjustments":{"type":"array","items":{"type":"string"}}}}}}}}}},"/session/swap-options":{"get":{"summary":"Same-muscle alternatives for an exercise (equipment/level/injury filtered)","parameters":[{"name":"exerciseId","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Options"}}}},"/session/swap":{"post":{"summary":"Swap an exercise (today-only or program-wide)","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["exerciseId","toExerciseId"],"properties":{"dayIndex":{"type":"integer"},"exerciseId":{"type":"string"},"toExerciseId":{"type":"string"},"scope":{"type":"string","enum":["today","program"],"default":"program"}}}}}},"responses":{"200":{"description":"Updated day"},"400":{"description":"Not a valid same-area swap"}}}},"/log":{"post":{"summary":"Log completed sets → progression outcomes + PRs","description":"Runs double-progression/e1RM; returns per-exercise events (established/progressed/held/deloaded) with next-time targets. Weights in kg.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["exercises"],"properties":{"dayIndex":{"type":"integer"},"goal":{"type":"string"},"exercises":{"type":"array","items":{"type":"object","properties":{"exerciseId":{"type":"string"},"prescribedRepTop":{"type":"integer"},"sets":{"type":"array","items":{"type":"object","properties":{"weightKg":{"type":"number"},"reps":{"type":"integer"},"rpe":{"type":"number","nullable":true},"completed":{"type":"boolean"}}}}}}}}}}}},"responses":{"200":{"description":"outcomes + prs"}}}},"/checkin":{"post":{"summary":"Readiness check-in (energy/sleep/soreness 1–5) → score + modifier","description":"Persisted per day; the next /session/today reshapes the workout accordingly.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["energy","sleep","soreness"],"properties":{"energy":{"type":"integer","minimum":1,"maximum":5},"sleep":{"type":"integer","minimum":1,"maximum":5},"soreness":{"type":"integer","minimum":1,"maximum":5}}}}}},"responses":{"200":{"description":"{ score, modifier, reasons }"}}}},"/session/adjust":{"post":{"summary":"Preview today's session reshaped for a readiness modifier","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"dayIndex":{"type":"integer"},"modifier":{"type":"string","enum":["full","reduce_volume","reduce_intensity","swap_to_recovery"]},"energy":{"type":"integer"},"sleep":{"type":"integer"},"soreness":{"type":"integer"}}}}}},"responses":{"200":{"description":"Adjusted day + targets + coach summary"}}}},"/nutrition/targets":{"get":{"summary":"Daily calorie + macro targets","responses":{"200":{"description":"Targets","content":{"application/json":{"schema":{"type":"object","properties":{"targets":{"$ref":"#/components/schemas/NutritionTargets"}}}}}}}}},"/nutrition/meal-plan":{"get":{"summary":"A day of meals hitting the targets (USDA truth-checked, ~15s AI call)","parameters":[{"name":"mealsPerDay","in":"query","schema":{"type":"integer","minimum":3,"maximum":5}},{"name":"trainingDay","in":"query","schema":{"type":"boolean"}},{"name":"dietaryStyle","in":"query","schema":{"type":"string"}},{"name":"exclusions","in":"query","schema":{"type":"string"},"description":"Comma-separated."}],"responses":{"200":{"description":"Plan (feasible=false + note when macros are approximate)"}}}},"/nutrition/food-log":{"post":{"summary":"Log food (barcode | usda | custom)","responses":{"200":{"description":"Entry"}}}},"/changelog":{"get":{"summary":"'What changed & why' — the engine's coaching decisions, in your configured coach voice","parameters":[{"name":"limit","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"events[]"}}}},"/me/mcp-token":{"post":{"summary":"Mint a personal agent token for the authenticated user (JWT auth)","responses":{"201":{"description":"fft_ token, shown once"}}}}}}