{"openapi":"3.0.3","info":{"title":"YMove Fitness API","version":"2.5.0","description":"Access 1110+ professional exercise videos, a comprehensive food & nutrition database, recipe search, and AI-powered meal plan generation. Features include rate limiting, monthly exercise caps, and video access protection. Video URLs are pre-signed and expire after 48 hours. Always fetch fresh exercise data before displaying videos. Sign up at https://ymove.app/exercise-api/signup for a free trial.","contact":{"email":"contact@ymove.app","url":"https://ymove.app/exercise-api"}},"servers":[{"url":"https://exercise-api.ymove.app/api/v2","description":"Production"}],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"components":{"securitySchemes":{"ApiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key"},"ApiKeyQuery":{"type":"apiKey","in":"query","name":"api_key"}},"schemas":{"Thumbnails":{"type":"object","description":"Five framings of one frame from the clip, all served from ymove.app. Unlike the video URLs these are static: no token, no expiry, safe to cache, store and hotlink. They accompany the video whenever one is returned, and never count against your monthly cap. On Scale they are also returned without a video, in browse mode and with excludeVideos; on the capped plans they are not, so the monthly exercise limit stays visible while you build rather than arriving all at once the day you switch video on. The frame is picked from across the clip for a visible face and the most extended pose, so a jump is caught mid-air rather than at the standing rest position the clip opens on. Every crop is centred on the person by face detection, and each one is white-balanced after cropping, so a grid of clips from different shoots reads as one library instead of drifting warm or green between shoots.","required":["default","square","portrait","landscape","original"],"properties":{"default":{"type":"string","format":"uri","description":"Up to 600x800 (3:4). The one to reach for, and what thumbnailUrl points at. A mild portrait holds a standing lift and a lying floor exercise without cramping either."},"square":{"type":"string","format":"uri","description":"Up to 600x600 (1:1). List rows, grid tiles, anywhere the layout is already square."},"portrait":{"type":"string","format":"uri","description":"Up to 720x1280 (9:16). Matches the video itself, for full-bleed mobile and story-style screens."},"landscape":{"type":"string","format":"uri","description":"Up to 1280x720 (16:9). Wide cards, hero strips, and the poster frame of a video player. Tall poses are cropped to the upper body to fit the shape. Sizes are ceilings: the images are never upscaled, so a crop is only as large as the source clip allows, and older clips cap around 720px wide."},"original":{"type":"string","format":"uri","description":"The whole frame, uncropped, as shot (portrait, ~9:16). Use it when you want to crop it yourself."}}},"Video":{"type":"object","required":["tag","isPrimary"],"description":"Video URLs are pre-signed and expire after 48 hours. Do not cache these URLs. Always fetch fresh exercise data before displaying videos.","properties":{"videoUrl":{"type":"string","format":"uri","description":"Pre-signed CDN URL for MP4 video. Expires after 48 hours."},"videoHlsUrl":{"type":"string","format":"uri","description":"Pre-signed CDN URL for HLS playlist. Expires after 48 hours."},"thumbnailUrl":{"type":"string","format":"uri","description":"The default thumbnail (up to 600x800, 3:4), served from ymove.app. Static and cacheable - it does not expire. Same as thumbnails.default; the other framings are on the thumbnails object."},"thumbnails":{"$ref":"#/components/schemas/Thumbnails"},"tag":{"type":"string","enum":["white-background","gym-shot"]},"orientation":{"type":"string","enum":["landscape","portrait"],"description":"Video aspect orientation. Most exercise clips are PORTRAIT (vertical, ~9:16), shot for mobile. Do not assume landscape. Read this field and size your player to a portrait container (e.g. aspect-ratio 9/16) with object-fit: cover so clips fill the frame instead of letterboxing."},"isPrimary":{"type":"boolean"}}},"Exercise":{"type":"object","required":["id","title","slug","muscleGroup","equipment","hasVideo","hasVideoWhite","hasVideoGym","videos"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string","nullable":true},"instructions":{"type":"array","items":{"type":"string"},"nullable":true},"importantPoints":{"type":"array","items":{"type":"string"},"nullable":true},"muscleGroup":{"type":"string"},"secondaryMuscles":{"type":"array","items":{"type":"string"},"nullable":true},"equipment":{"type":"string"},"category":{"type":"string","nullable":true},"difficulty":{"type":"string","nullable":true},"videoUrl":{"type":"string","format":"uri","nullable":true,"description":"Pre-signed CDN URL for primary video. Expires after 48 hours."},"videoHlsUrl":{"type":"string","format":"uri","nullable":true,"description":"Pre-signed CDN URL for HLS playlist. Expires after 48 hours."},"thumbnailUrl":{"type":"string","format":"uri","nullable":true,"description":"Default thumbnail (up to 600x800, 3:4) for the primary video, served from ymove.app. Static and cacheable - it does not expire. Same as thumbnails.default. Returned in browse mode too, where the video URLs are not."},"thumbnails":{"$ref":"#/components/schemas/Thumbnails"},"videoDurationSecs":{"type":"integer","nullable":true},"exerciseType":{"type":"array","items":{"type":"string"},"nullable":true},"hasVideo":{"type":"boolean"},"hasVideoWhite":{"type":"boolean"},"hasVideoGym":{"type":"boolean"},"videos":{"type":"array","items":{"$ref":"#/components/schemas/Video"}}},"example":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","title":"Barbell Bench Press","slug":"barbell-bench-press","description":"A compound chest exercise performed lying on a flat bench.","instructions":["Lie on a flat bench with feet flat on the floor","Grip the barbell slightly wider than shoulder width","Lower the bar to mid-chest","Press the bar back up to full arm extension"],"importantPoints":["Keep your back flat on the bench","Do not bounce the bar off your chest"],"muscleGroup":"chest","secondaryMuscles":["triceps","shoulders"],"equipment":"barbell","category":"compound","difficulty":"intermediate","videoUrl":"https://vz-xxx.b-cdn.net/video-id/play_720p.mp4?token=...","videoHlsUrl":"https://vz-xxx.b-cdn.net/video-id/playlist.m3u8?token=...","thumbnailUrl":"https://ymove.app/api/v2/thumbnail/video-id?crop=default","thumbnails":{"default":"https://ymove.app/api/v2/thumbnail/video-id?crop=default","square":"https://ymove.app/api/v2/thumbnail/video-id?crop=square","portrait":"https://ymove.app/api/v2/thumbnail/video-id?crop=portrait","landscape":"https://ymove.app/api/v2/thumbnail/video-id?crop=landscape","original":"https://ymove.app/api/v2/thumbnail/video-id"},"videoDurationSecs":12,"exerciseType":["strength"],"hasVideo":true,"hasVideoWhite":true,"hasVideoGym":false,"videos":[{"videoUrl":"https://vz-xxx.b-cdn.net/video-id/play_720p.mp4?token=...","videoHlsUrl":"https://vz-xxx.b-cdn.net/video-id/playlist.m3u8?token=...","thumbnailUrl":"https://ymove.app/api/v2/thumbnail/video-id?crop=default","thumbnails":{"default":"https://ymove.app/api/v2/thumbnail/video-id?crop=default","square":"https://ymove.app/api/v2/thumbnail/video-id?crop=square","portrait":"https://ymove.app/api/v2/thumbnail/video-id?crop=portrait","landscape":"https://ymove.app/api/v2/thumbnail/video-id?crop=landscape","original":"https://ymove.app/api/v2/thumbnail/video-id"},"tag":"white-background","orientation":"landscape","isPrimary":true}]}},"Pagination":{"type":"object","required":["page","pageSize","total","totalPages"],"properties":{"page":{"type":"integer"},"pageSize":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}}},"MuscleGroup":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"exerciseCount":{"type":"integer"}}},"WorkoutExercise":{"type":"object","properties":{"exercise":{"$ref":"#/components/schemas/Exercise"},"sets":{"type":"integer"},"reps":{"type":"string"},"restSeconds":{"type":"integer"},"group":{"type":"string","description":"Block label when structure=superset (\"A\",\"B\",...) or circuit (\"circuit\"). Absent on straight sets and unpaired exercises."},"groupType":{"type":"string","enum":["superset","circuit"],"description":"Block type; absent on straight sets."},"groupPosition":{"type":"integer","description":"1-based position inside the block."},"order":{"type":"integer"}}},"Workout":{"type":"object","properties":{"name":{"type":"string"},"muscleGroups":{"type":"array","items":{"type":"string"},"description":"Muscle groups targeted (expanded from any aliases in the request)."},"muscleGroupsRequested":{"type":"array","items":{"type":"string"},"description":"The original muscleGroup values from the request, including any aliases (full_body, upper_body, lower_body)."},"difficulty":{"type":"string"},"estimatedMinutes":{"type":"integer","description":"Estimated total workout duration, including warmup and cooldown when present."},"exerciseCount":{"type":"integer"},"exercises":{"type":"array","items":{"$ref":"#/components/schemas/WorkoutExercise"}},"structure":{"type":"string","enum":["straight","superset","circuit"],"description":"Structure actually used (auto resolves to one of these)."},"structureReason":{"type":"string","description":"Why auto picked this structure. Only present when structure=auto was requested or defaulted."},"warmup":{"type":"array","items":{"$ref":"#/components/schemas/WorkoutExercise"},"description":"Warmup exercises (bodyweight stretches, mobility, and light cardio) placed at the start of the session. Present unless includeWarmup=false."},"cooldown":{"type":"array","items":{"$ref":"#/components/schemas/WorkoutExercise"},"description":"Cooldown exercises (bodyweight stretches and mobility) placed at the end of the session. Present unless includeCooldown=false."}}},"ProgramDay":{"type":"object","properties":{"day":{"type":"integer"},"name":{"type":"string"},"muscleGroups":{"type":"array","items":{"type":"string"}},"exercises":{"type":"array","items":{"$ref":"#/components/schemas/WorkoutExercise"}},"warmup":{"type":"array","items":{"$ref":"#/components/schemas/WorkoutExercise"},"description":"Warmup exercises (bodyweight stretches, mobility, and light cardio) placed at the start of this day. Present unless includeWarmup=false."},"cooldown":{"type":"array","items":{"$ref":"#/components/schemas/WorkoutExercise"},"description":"Cooldown exercises (bodyweight stretches and mobility) placed at the end of this day. Present unless includeCooldown=false."}}},"Program":{"type":"object","properties":{"name":{"type":"string"},"goal":{"type":"string"},"difficulty":{"type":"string"},"daysPerWeek":{"type":"integer"},"weeks":{"type":"integer"},"split":{"type":"string"},"weeklySchedule":{"type":"array","items":{"$ref":"#/components/schemas/ProgramDay"}},"notes":{"type":"string"}}},"PostureIssue":{"type":"object","properties":{"name":{"type":"string","description":"Issue identifier (e.g. \"forward_head\", \"rounded_shoulders\", \"knee_valgus\")."},"severity":{"type":"string","enum":["mild","moderate","severe"]},"description":{"type":"string","description":"Human-readable description of the issue detected."},"how_to_fix":{"type":"string","description":"Plain-language instructions on how to correct the issue."},"exercises":{"type":"array","description":"Corrective exercises with video URLs.","items":{"$ref":"#/components/schemas/CorrectiveExercise"}}}},"CorrectiveExercise":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"slug":{"type":"string"},"video_url":{"type":"string","format":"uri","nullable":true},"thumbnail_url":{"type":"string","format":"uri","nullable":true},"duration":{"type":"string","description":"Recommended sets/reps (e.g. \"30s hold x 3 sets each side\")."}}},"PostureAnalysisResult":{"type":"object","properties":{"data":{"type":"object","properties":{"score":{"type":"integer","description":"Form score from 0 (poor) to 100 (perfect)."},"issues":{"type":"array","items":{"$ref":"#/components/schemas/PostureIssue"}},"summary":{"type":"string","description":"Overall summary of the analysis."},"frames_analyzed":{"type":"integer","description":"Number of video frames analyzed."},"analysis_type":{"type":"string","enum":["standing","seated","walking","exercise","general"]},"exercise_name":{"type":"string"},"llm_used":{"type":"string"},"disclaimer":{"type":"string"}}},"usage":{"type":"object","properties":{"posture_analyses_used":{"type":"integer"},"posture_analyses_included":{"type":"integer"},"is_overage":{"type":"boolean"},"overage_charge":{"type":"number"}}}}},"Usage":{"type":"object","properties":{"plan":{"type":"string"},"status":{"type":"string","description":"Account status (e.g. \"active\", \"trial\")"},"minutesUsed":{"type":"integer"},"minutesLimit":{"type":"integer"},"minutesRemaining":{"type":"integer"},"percentUsed":{"type":"integer"},"rateLimit":{"type":"integer","description":"Requests per minute allowed for your plan"},"monthlyExercisesUsed":{"type":"integer","description":"Distinct exercises accessed in last 30 days"},"monthlyExerciseLimit":{"oneOf":[{"type":"integer"},{"type":"string","enum":["unlimited"]}],"description":"Max unique exercises per month for your plan. Returns \"unlimited\" for yearly plans."},"postureAnalyses":{"type":"object","properties":{"used":{"type":"integer"},"included":{"type":"integer"},"remaining_free":{"type":"integer"},"overage_count":{"type":"integer"},"overage_charge":{"type":"number"},"price_per_extra":{"type":"number"}}},"whiteVideoAccess":{"type":"boolean"},"premiumVideoAccess":{"type":"boolean"},"trialEndsAt":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 timestamp when trial expires (null for non-trial accounts)"}}},"ExerciseType":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"exerciseCount":{"type":"integer"}}},"RateLimitError":{"type":"object","required":["error","retryAfterMs"],"properties":{"error":{"type":"string"},"retryAfterMs":{"type":"integer","description":"Milliseconds until the rate limit resets"},"upgradeUrl":{"type":"string","description":"URL to upgrade your plan"},"availablePlans":{"type":"array","items":{"type":"string"}}}},"MonthlyCapWarning":{"type":"object","description":"Included as `_warning` in the response body when the monthly exercise cap is exceeded. Exercises not previously accessed will have their video properties (videoUrl, videoHlsUrl, thumbnailUrl, videoDurationSecs, videos) removed entirely from the response. Previously accessed exercises keep their video URLs.","properties":{"message":{"type":"string"},"reason":{"type":"string","enum":["monthly_exercise_cap"]},"monthlyExercisesUsed":{"type":"integer"},"monthlyExerciseLimit":{"type":"integer"},"upgradeUrl":{"type":"string"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"message":{"type":"string"}}},"Plan":{"type":"object","required":["plan","minutesLimit","postureLimit","brandLimit"],"properties":{"plan":{"type":"string","enum":["basic","pro","scale","enterprise"]},"minutesLimit":{"type":"integer"},"postureLimit":{"type":"integer"},"brandLimit":{"type":"integer"}}},"Food":{"type":"object","description":"All weights default to grams unless noted. Sodium and cholesterol are in milligrams. Three name fields: `shortName` is a compact label for search results and lists. `displayName` is a detailed label that distinguishes similar foods (includes cooking method, grade, brand). `name` is the raw database entry. Use `shortName` for compact views, `displayName` for detail views. The database includes international products from OpenFoodFacts with names in their original language (Dutch, French, German, Spanish, and more). The API is optimized for English, but searching in other languages works for branded and international products.","properties":{"id":{"type":"string"},"fdcId":{"type":"integer","nullable":true,"description":"USDA FoodData Central ID"},"name":{"type":"string","description":"Full original name from the source database (e.g. USDA). Often long and technical - not recommended for UI display. Example: \"Chicken, broilers or fryers, breast, skinless, boneless, meat only, cooked, grilled\""},"shortName":{"type":"string","description":"Short, search-friendly label. Use this for search results, autocomplete dropdowns, and compact list views where space is limited. Example: \"Chicken Breast\", \"Brown Rice\", \"Granola Bar\""},"displayName":{"type":"string","description":"Detailed human-readable name that distinguishes similar foods. Use this for food detail pages, nutrition labels, and anywhere users need to tell similar foods apart. Includes cooking method, grade, and preparation for USDA foods. Includes brand in parentheses for packaged products. Examples: \"Chicken Breast (grilled, lean)\", \"Top Round Steak (choice, lean, broiled)\", \"Granola Bar (Nature Valley)\""},"brand":{"type":"string","nullable":true,"description":"Brand name for packaged/branded products. Null for generic foods (e.g. USDA entries). Also included in displayName when present."},"category":{"type":"string","nullable":true,"description":"Food category (e.g. \"Poultry Products\", \"Fruits and Fruit Juices\", \"Cereals\")"},"servingSize":{"type":"number","description":"Serving size in grams. All nutritional values (calories, protein, fat, etc.) are per this serving size. To calculate nutrition per 100g: divide each value by servingSize and multiply by 100."},"servingDescription":{"type":"string","nullable":true,"description":"Human-readable serving description (e.g. \"1 cup (240ml)\", \"1 medium (182g)\", \"1 slice (28g)\"). Display this to users alongside servingSize for context."},"calories":{"type":"number","description":"Calories (kcal) per serving"},"protein":{"type":"number","description":"Protein per serving in grams. All macro values are in grams unless noted."},"fat":{"type":"number","description":"Total fat per serving (grams)"},"carbs":{"type":"number","description":"Carbohydrates per serving (grams)"},"fiber":{"type":"number","nullable":true,"description":"Dietary fiber per serving (grams)"},"sugar":{"type":"number","nullable":true,"description":"Sugar per serving (grams)"},"sodium":{"type":"number","nullable":true,"description":"Sodium per serving in milligrams (not grams - only sodium and cholesterol use mg)"},"cholesterol":{"type":"number","nullable":true,"description":"Cholesterol per serving in milligrams"},"saturatedFat":{"type":"number","nullable":true,"description":"Saturated fat per serving (grams)"},"barcode":{"type":"string","nullable":true},"imageUrl":{"type":"string","nullable":true,"description":"Product image URL. Sourced from Open Food Facts for branded products and enriched from Open Food Facts / curated staples for generic USDA items. May be null when no image has been located for the food."},"source":{"type":"string","description":"Data source (e.g. \"usda\", \"openfoodfacts\")"},"country":{"type":"string","nullable":true,"description":"ISO 3166-1 alpha-2 country code (e.g. \"FR\", \"US\", \"NL\") when the product is tagged to a specific country. Null for USDA generic items and untagged products."}},"example":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","fdcId":171077,"name":"Chicken, broilers or fryers, breast, skinless, boneless, meat only, cooked, grilled","shortName":"Chicken Breast","displayName":"Chicken Breast (grilled, lean)","brand":null,"category":"Poultry Products","servingSize":100,"servingDescription":"100g","calories":165,"protein":31,"fat":3.6,"carbs":0,"fiber":0,"sugar":0,"sodium":74,"cholesterol":85,"saturatedFat":1,"barcode":null,"source":"usda"}},"FoodAnalysisResult":{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"AI-identified food name"},"estimatedGrams":{"type":"number","description":"Estimated portion in grams"},"confidence":{"type":"string","enum":["high","medium","low"],"description":"AI confidence level"},"matchedFood":{"type":"object","nullable":true,"description":"Matched food from the database (null if no match)","properties":{"id":{"type":"string"},"name":{"type":"string"},"servingSizeG":{"type":"number"}}},"nutrition":{"type":"object","nullable":true,"description":"Calculated nutrition based on matched food and estimated portion (null if no match)","properties":{"calories":{"type":"number"},"protein":{"type":"number"},"fat":{"type":"number"},"carbs":{"type":"number"}}}}}},"totals":{"type":"object","properties":{"calories":{"type":"number"},"protein":{"type":"number"},"fat":{"type":"number"},"carbs":{"type":"number"}}}}},"usage":{"type":"object","properties":{"analyses_used":{"type":"integer"},"analyses_included":{"type":"integer"},"is_overage":{"type":"boolean"},"overage_charge":{"type":"number"}}}}},"GlycemicAnalysisResult":{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"AI-identified ingredient name"},"estimatedGrams":{"type":"number","description":"Estimated portion in grams"},"confidence":{"type":"string","enum":["high","medium","low"]},"matchedFood":{"type":"object","nullable":true,"description":"Matched food from the database (null if no match)","properties":{"id":{"type":"string"},"name":{"type":"string"},"servingSizeG":{"type":"number"}}},"nutrition":{"type":"object","nullable":true,"properties":{"calories":{"type":"number"},"protein":{"type":"number"},"fat":{"type":"number"},"carbs":{"type":"number"}}},"glycemicIndex":{"type":"number","nullable":true,"description":"Glucose-referenced GI (0-110). Null for foods with no meaningful available carbohydrate."},"giBasis":{"type":"string","enum":["published","estimated","not_applicable"],"description":"Whether the GI is a published value, inferred from a similar food, or not applicable"},"matchQuality":{"type":"string","nullable":true,"enum":["ok","state_mismatch","unrelated"],"description":"Quality of the catalog match. 'state_mismatch' means the right food in the wrong preparation (carbohydrate per gram can be several-fold off); an 'unrelated' match is dropped, so it never appears with a matchedFood attached."},"fiberG":{"type":"number","nullable":true},"availableCarbsG":{"type":"number","nullable":true,"description":"Carbohydrate minus fiber, from the matched catalog food"},"glycemicLoad":{"type":"number","nullable":true,"description":"glycemicIndex x availableCarbsG / 100. Null when the ingredient could not be matched."},"glycemicLoadCategory":{"type":"string","nullable":true,"enum":["low","medium","high"]}}}},"totals":{"type":"object","properties":{"calories":{"type":"number"},"protein":{"type":"number"},"fat":{"type":"number"},"carbs":{"type":"number"},"fiber":{"type":"number"},"availableCarbs":{"type":"number"}}},"meal":{"type":"object","properties":{"glycemicLoad":{"type":"number","description":"Sum of the matched ingredients glycemic loads"},"glycemicLoadCategory":{"type":"string","enum":["low","medium","high"]},"estimatedGlycemicIndex":{"type":"number","nullable":true,"description":"Available-carbohydrate weighted mean GI of the meal"},"unmatchedItems":{"type":"array","items":{"type":"string"},"description":"Ingredients with no catalog match, and therefore no load contribution"},"flaggedItems":{"type":"array","items":{"type":"string"},"description":"Ingredients counted in the total whose match was flagged state_mismatch"},"scale":{"type":"object","description":"The cut-offs used for the category fields","properties":{"item":{"type":"string"},"meal":{"type":"string"}}}}},"advice":{"type":"array","description":"Concrete changes to this meal that lower its glycemic impact","items":{"type":"object","properties":{"suggestion":{"type":"string"},"rationale":{"type":"string"},"swap":{"type":"object","description":"Present when the advice is an ingredient swap. Numbers are recomputed for the same gram weight when the replacement matches the catalog.","properties":{"from":{"type":"string"},"to":{"type":"string"},"glycemicIndex":{"type":"number","nullable":true},"matchedFood":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"glycemicLoad":{"type":"number","nullable":true},"glycemicLoadDelta":{"type":"number","nullable":true,"description":"Negative means the swap lowers the load"},"projectedMealGlycemicLoad":{"type":"number","nullable":true}}}}}},"disclaimer":{"type":"string"}}},"usage":{"type":"object","properties":{"analyses_used":{"type":"integer"},"analyses_included":{"type":"integer"},"is_overage":{"type":"boolean"},"overage_charge":{"type":"number"}}}}},"Recipe":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"cuisine":{"type":"string"},"diet":{"type":"array","items":{"type":"string"}},"mealType":{"type":"string"},"prepTimeMinutes":{"type":"integer"},"cookTimeMinutes":{"type":"integer"},"servings":{"type":"integer"},"calories":{"type":"number"},"protein":{"type":"number"},"carbs":{"type":"number"},"fat":{"type":"number"},"fiber":{"type":"number"},"ingredients":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"amount":{"type":"string"},"calories":{"type":"number"},"protein":{"type":"number"}}}},"instructions":{"type":"array","items":{"type":"string"}}},"example":{"id":"grilled-chicken-salad","title":"Grilled Chicken Salad","description":"A healthy high-protein salad with grilled chicken.","cuisine":"american","diet":["high_protein","low_carb"],"mealType":"lunch","prepTimeMinutes":15,"cookTimeMinutes":10,"servings":2,"calories":350,"protein":38,"carbs":12,"fat":16}},"MealPlan":{"type":"object","properties":{"calories":{"type":"integer","description":"Daily calorie target"},"diet":{"type":"string"},"macroSplit":{"type":"string"},"mealsPerDay":{"type":"integer","description":"Number of meals per day"},"daysCount":{"type":"integer","description":"Number of days in this plan (1-7)"},"averageDailyTotals":{"type":"object","description":"Average per-day macros across all days in the plan.","properties":{"calories":{"type":"number"},"protein":{"type":"number"},"carbs":{"type":"number"},"fat":{"type":"number"}}},"days":{"type":"array","description":"Per-day breakdown of the plan. Always populated, even for daysCount=1.","items":{"type":"object","properties":{"dayIndex":{"type":"integer","description":"1-based day number"},"totals":{"type":"object","properties":{"calories":{"type":"number"},"protein":{"type":"number"},"carbs":{"type":"number"},"fat":{"type":"number"}}},"meals":{"type":"array","items":{"$ref":"#/components/schemas/MealPlanMeal"}}}}},"totals":{"type":"object","description":"Backward-compatible alias for days[0].totals (day 1 only). For multi-day plans use averageDailyTotals or per-day totals in days[].","properties":{"calories":{"type":"number"},"protein":{"type":"number"},"carbs":{"type":"number"},"fat":{"type":"number"}}},"meals":{"type":"array","description":"Backward-compatible alias for days[0].meals (day 1 only).","items":{"$ref":"#/components/schemas/MealPlanMeal"}}}},"MealPlanMeal":{"type":"object","properties":{"type":{"type":"string","description":"breakfast | lunch | dinner | snack"},"name":{"type":"string","description":"Recipe title (same as recipe.title)"},"recipeId":{"type":"string","description":"Recipe.id - use to look up the full recipe or fetch its image"},"recipeSlug":{"type":"string"},"imageUrl":{"type":"string","nullable":true},"portionMultiplier":{"type":"number","description":"Multiplier applied to the recipe (0.5-2.0). Macros + ingredient quantities are already scaled."},"calories":{"type":"number","description":"Already scaled by portionMultiplier"},"protein":{"type":"number"},"carbs":{"type":"number"},"fat":{"type":"number"},"recipe":{"type":"object","description":"Full recipe metadata so clients can render title + prep time + servings + image without a second API call.","properties":{"id":{"type":"string"},"title":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string","nullable":true},"mealType":{"type":"string"},"cuisineType":{"type":"string","nullable":true},"difficulty":{"type":"string","nullable":true},"prepTimeMin":{"type":"integer"},"cookTimeMin":{"type":"integer"},"servings":{"type":"integer"},"dietTags":{"type":"array","items":{"type":"string"}},"instructions":{"type":"array","items":{"type":"string"},"description":"Ordered cooking steps, so clients can render the full recipe without a second call."},"imageUrl":{"type":"string","nullable":true}}},"foods":{"type":"array","description":"Recipe ingredients with quantities + macros already scaled by portionMultiplier.","items":{"type":"object","properties":{"name":{"type":"string"},"portion":{"type":"string","description":"e.g. \"200 g\", \"1.5 tbsp\""},"calories":{"type":"number"},"protein":{"type":"number"},"carbs":{"type":"number"},"fat":{"type":"number"}}}}}},"DietType":{"type":"object","properties":{"diet":{"type":"string","description":"Diet tag identifier (e.g. high_protein, keto, vegan)"},"count":{"type":"integer","description":"Number of recipes with this diet tag"}}},"MealType":{"type":"object","properties":{"mealType":{"type":"string","description":"Meal type (e.g. breakfast, lunch, dinner, snack)"},"count":{"type":"integer","description":"Number of recipes for this meal type"}}}}},"paths":{"/exercises":{"get":{"summary":"List and search exercises","operationId":"listExercises","tags":["Exercises"],"parameters":[{"name":"muscleGroup","in":"query","example":"chest","schema":{"type":"string","enum":["chest","back","shoulders","biceps","triceps","forearms","quads","hamstrings","glutes","calves","core","full_body","abs","cardio"]},"description":"Filter by muscle group"},{"name":"exerciseType","in":"query","schema":{"type":"string","enum":["strength","yoga","stretching","cardio","plyometric","calisthenics","warmup","cooldown","balance","mobility","isometric","rehabilitation","functional","core","hiit"]},"description":"Filter by exercise type"},{"name":"equipment","in":"query","schema":{"type":"string","enum":["machine","barbell","dumbbell","kettlebell","bodyweight","cable","medicine ball","weighted vest"]},"description":"Filter by equipment"},{"name":"difficulty","in":"query","schema":{"type":"string","enum":["beginner","intermediate","advanced"]},"description":"Filter by difficulty"},{"name":"hasVideo","in":"query","example":true,"schema":{"type":"boolean"},"description":"Only return exercises with video"},{"name":"hasVideoWhite","in":"query","schema":{"type":"boolean"},"description":"Only exercises with white-background video"},{"name":"hasVideoGym","in":"query","schema":{"type":"boolean"},"description":"Only exercises with gym-shot video"},{"name":"videoTag","in":"query","schema":{"type":"string","enum":["white-background","gym-shot"]},"description":"Filter video array by tag"},{"name":"search","in":"query","schema":{"type":"string"},"description":"Search by exercise title"},{"name":"page","in":"query","example":1,"schema":{"type":"integer","default":1,"minimum":1},"description":"Page number"},{"name":"pageSize","in":"query","example":3,"schema":{"type":"integer","default":20,"minimum":1,"maximum":50},"description":"Results per page"},{"name":"includeVideos","in":"query","schema":{"type":"boolean"},"description":"Whether to include video URLs in the response. When omitted, the default depends on your API key: keys created before 2026-07-07 default to true, newer keys default to false (browse mode) so paginating cannot burn your monthly cap by accident. Pass it explicitly to be unambiguous. true: video fields are returned and each listed exercise counts toward your monthly cap. false: browse mode - no videoUrl or videoHlsUrl, no monthly-cap charge. Thumbnails accompany the video and never count toward the cap; on Scale they are returned in browse mode too, on the capped plans they are not."},{"name":"excludeVideos","in":"query","schema":{"type":"boolean"},"description":"The same switch from the other side, for parity with /exercises/{id}. excludeVideos=1 is browse mode regardless of your key default, and wins if includeVideos is also sent. On the capped plans this drops the thumbnails with the video; on Scale the thumbnails remain."}],"responses":{"200":{"description":"Paginated list of exercises. When the monthly exercise cap is exceeded, exercises not previously accessed will have their video URLs (videoUrl, videoHlsUrl, videoDurationSecs) removed. A `_warning` object is included with details. Previously accessed exercises keep their video URLs. Thumbnails accompany any returned video; Scale keeps them in browse mode as well.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Exercise"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"_warning":{"$ref":"#/components/schemas/MonthlyCapWarning"}}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}}}}},"/exercises/exercise-types":{"get":{"summary":"List all exercise types with counts","operationId":"listExerciseTypes","tags":["Exercises"],"responses":{"200":{"description":"List of exercise types","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ExerciseType"}}}}}}}}}},"/exercises/muscle-groups":{"get":{"summary":"List all muscle groups with exercise counts","operationId":"listMuscleGroups","tags":["Exercises"],"responses":{"200":{"description":"List of muscle groups","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MuscleGroup"}}}}}}}}}},"/exercises/equipment":{"get":{"summary":"List all equipment types with exercise counts","operationId":"listEquipment","tags":["Exercises"],"responses":{"200":{"description":"List of equipment types, sorted by exercise count","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MuscleGroup"}}}}}}}}}},"/exercises/{id}":{"get":{"summary":"Get a single exercise by ID or slug","operationId":"getExercise","tags":["Exercises"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Exercise UUID or slug"},{"name":"excludeVideos","in":"query","schema":{"type":"boolean"},"description":"Videos are included by default here - you asked for one specific exercise, you probably want to play it. Pass excludeVideos=1 to get the metadata without the video URLs, in which case this exercise does not count toward your monthly cap. Thumbnails come with it on Scale; on the capped plans they go with the video. includeVideos=false does the same thing."}],"responses":{"200":{"description":"Exercise details. Videos are included by default; pass excludeVideos=1 for metadata and thumbnails only, which costs nothing. When the monthly exercise cap is exceeded and this exercise was not previously accessed, the video URLs are removed and a `_warning` is included. Thumbnails accompany any returned video; on Scale they are returned even when no video is.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Exercise"},"_warning":{"$ref":"#/components/schemas/MonthlyCapWarning"}}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Exercise not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}}}}},"/workouts/generate":{"get":{"summary":"Generate a structured workout","operationId":"generateWorkout","tags":["Workouts"],"parameters":[{"name":"muscleGroup","in":"query","required":true,"style":"form","explode":false,"schema":{"type":"array","items":{"type":"string","enum":["chest","back","shoulders","biceps","triceps","forearms","quads","hamstrings","glutes","calves","core","full_body","upper_body","lower_body"]}},"description":"Target muscle group(s). Accepts a single value, a comma-separated list (e.g. \"chest,back\"), or repeated query keys. Aliases: \"full_body\" (all groups), \"upper_body\" (chest, back, shoulders, biceps, triceps, forearms, core), \"lower_body\" (quads, hamstrings, glutes, calves). Aliases can be combined with specific groups."},{"name":"equipment","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string","enum":["machine","barbell","dumbbell","kettlebell","bodyweight","cable","medicine ball","weighted vest"]}},"description":"Limit to specific equipment. Accepts a single value, comma-separated list, or repeated keys."},{"name":"exerciseType","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string","enum":["strength","yoga","stretching","cardio","plyometric","calisthenics","warmup","cooldown","balance","mobility","isometric","rehabilitation","functional","core","hiit"]}},"description":"Filter main exercises by type. Accepts a single value, comma-separated list, or repeated keys."},{"name":"difficulty","in":"query","schema":{"type":"string","enum":["beginner","intermediate","advanced"],"default":"intermediate"},"description":"Difficulty level. Filters which exercises are selected: beginner returns beginner-level moves, intermediate returns beginner+intermediate, advanced prefers advanced moves (falling back to intermediate where advanced options are limited). Also sets the default set/rep/rest scheme."},{"name":"exerciseCount","in":"query","schema":{"type":"integer","default":6,"minimum":3,"maximum":12},"description":"Number of main exercises (warmup and cooldown are returned separately and counted in addition to this)."},{"name":"structure","in":"query","schema":{"type":"string","enum":["straight","superset","circuit","auto"],"default":"straight"},"description":"Workout structure. auto lets the generator pick: straight for advanced (heavy work needs full rest) or single-muscle requests, circuit for bodyweight-only sessions of 5+ exercises, supersets when multiple muscle groups allow antagonist pairing; the response then carries structureReason. Keys created after 2026-08-19 default to auto; older keys default to straight. straight (default): classic straight sets, unchanged output. superset: exercises are paired into blocks (group \"A\", \"B\", ... with groupType and groupPosition on each exercise), short rest inside the pair (supersetRestSeconds), full rest after it; pairs mix muscle groups where possible. circuit: all main exercises form one circuit performed for `rounds` rounds - each exercise carries sets=rounds, restSeconds is the hop between stations (stationRestSeconds), and roundRestSeconds separates rounds."},{"name":"rounds","in":"query","schema":{"type":"integer","minimum":2,"maximum":6,"default":3},"description":"Circuit rounds (structure=circuit only)."},{"name":"includeWarmup","in":"query","schema":{"type":"boolean","default":true},"description":"Prepends a warmup block of bodyweight stretches, mobility work, and light cardio. Default true; set to false to omit. Warmup exercises count toward your monthly exercise cap."},{"name":"warmupCount","in":"query","schema":{"type":"integer","default":3,"minimum":2,"maximum":5},"description":"Number of warmup exercises. Only applied when includeWarmup is not false."},{"name":"includeCooldown","in":"query","schema":{"type":"boolean","default":true},"description":"Appends a cooldown block of bodyweight stretches and mobility work at the end of the session. Default true; set to false to omit. Cooldown exercises count toward your monthly exercise cap."},{"name":"cooldownCount","in":"query","schema":{"type":"integer","default":3,"minimum":2,"maximum":5},"description":"Number of cooldown exercises. Only applied when includeCooldown is not false."},{"name":"includeVideos","in":"query","schema":{"type":"boolean"},"description":"Whether to include video URLs. When omitted, the default depends on your API key: keys created before 2026-07-07 default to true, newer keys default to false (browse mode). Pass includeVideos=true when you want playable videos - the returned exercises then count toward your monthly cap. Thumbnails are returned either way on this endpoint, on every plan."}],"responses":{"200":{"description":"Generated workout. When the monthly exercise cap is exceeded, exercises not previously accessed will have video properties removed entirely. A `_warning` object is included with details.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Workout"},"_warning":{"$ref":"#/components/schemas/MonthlyCapWarning"}}}}}},"400":{"description":"Missing required parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}}}}},"/programs/generate":{"get":{"summary":"Generate a multi-week training program","operationId":"generateProgram","tags":["Programs"],"parameters":[{"name":"goal","in":"query","schema":{"type":"string","enum":["muscle_building","weight_loss","strength","endurance"],"default":"muscle_building"},"description":"Training goal. Sets the set/rep/rest scheme and exercises-per-muscle."},{"name":"difficulty","in":"query","schema":{"type":"string","enum":["beginner","intermediate","advanced"],"default":"intermediate"},"description":"Difficulty level. Filters which exercises are selected per muscle: beginner returns beginner-level moves, intermediate returns beginner+intermediate, advanced prefers advanced moves (falling back to intermediate where advanced options are limited). Combine with goal, which controls volume."},{"name":"daysPerWeek","in":"query","schema":{"type":"integer","minimum":3,"maximum":6,"default":4},"description":"Training days per week"},{"name":"weeks","in":"query","schema":{"type":"integer","enum":[4,8,12],"default":4},"description":"Program duration in weeks"},{"name":"equipment","in":"query","schema":{"type":"string","enum":["machine","barbell","dumbbell","kettlebell","bodyweight","cable","medicine ball","weighted vest"]},"description":"Limit to specific equipment"},{"name":"includeWarmup","in":"query","schema":{"type":"boolean","default":true},"description":"Adds a warmup block (bodyweight stretches, mobility, light cardio) to the start of each training day. Default true; set to false to omit. Warmup exercises count toward your monthly exercise cap."},{"name":"warmupCount","in":"query","schema":{"type":"integer","default":3,"minimum":2,"maximum":5},"description":"Number of warmup exercises per day. Only applied when includeWarmup is not false."},{"name":"includeCooldown","in":"query","schema":{"type":"boolean","default":true},"description":"Adds a cooldown block (bodyweight stretches and mobility) to the end of each training day. Default true; set to false to omit. Cooldown exercises count toward your monthly exercise cap."},{"name":"cooldownCount","in":"query","schema":{"type":"integer","default":3,"minimum":2,"maximum":5},"description":"Number of cooldown exercises per day. Only applied when includeCooldown is not false."},{"name":"includeVideos","in":"query","schema":{"type":"boolean"},"description":"Whether to include video URLs. When omitted, the default depends on your API key: keys created before 2026-07-07 default to true, newer keys default to false (browse mode). Pass includeVideos=true when you want playable videos - the returned exercises then count toward your monthly cap. Thumbnails are returned either way on this endpoint, on every plan."}],"responses":{"200":{"description":"Generated training program. When the monthly exercise cap is exceeded, exercises not previously accessed will have video properties removed entirely. A `_warning` object is included with details.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Program"},"_warning":{"$ref":"#/components/schemas/MonthlyCapWarning"}}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}}}}},"/posture/analyze":{"post":{"summary":"Analyze exercise form from video (max 100MB)","operationId":"analyzePosture","tags":["Form Analysis"],"description":"Upload an exercise video and get AI-powered form analysis. Returns a form score (0-100), detected issues with plain-language fixes, and corrective exercise recommendations.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["video","exercise_name"],"properties":{"video":{"type":"object","required":["type","data"],"description":"The exercise video to analyze.","properties":{"type":{"type":"string","enum":["base64","url"],"description":"How the video is provided: base64-encoded data or a public URL."},"data":{"type":"string","description":"Base64-encoded video data, or a public URL to the video file."},"media_type":{"type":"string","enum":["video/mp4","video/webm","video/mov","video/quicktime"],"default":"video/mp4"}}},"exercise_name":{"type":"string","description":"Name of the exercise being performed (e.g. \"barbell squat\", \"deadlift\").","minLength":1,"maxLength":200},"analysis_type":{"type":"string","enum":["standing","seated","walking","exercise","general"],"default":"exercise","description":"Type of analysis to perform."},"custom_instructions":{"type":"string","maxLength":500,"description":"Optional extra instructions for the analysis (e.g. \"focus on knee alignment\")."},"num_frames":{"type":"integer","minimum":4,"maximum":20,"default":16,"description":"Number of frames to extract from the video for analysis."}}}}}},"responses":{"200":{"description":"Form analysis result with score, issues, and corrective exercises","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostureAnalysisResult"}}}},"400":{"description":"Validation error or analysis failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}}}}},"/usage":{"get":{"summary":"Check API usage and limits (includes monthly exercise cap)","operationId":"getUsage","tags":["Account"],"responses":{"200":{"description":"Current usage data including monthly exercise cap","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Usage"}}}}}}}}},"/upgrade":{"get":{"summary":"Get available plans and billing portal URL","operationId":"getUpgradeInfo","tags":["Account"],"responses":{"200":{"description":"Available plans and current subscription info","content":{"application/json":{"schema":{"type":"object","required":["currentPlan","availablePlans","message"],"properties":{"currentPlan":{"type":"string","description":"Your current plan (e.g. \"demo\", \"basic\", \"pro\")"},"availablePlans":{"type":"array","items":{"$ref":"#/components/schemas/Plan"}},"billingPortalUrl":{"type":"string","format":"uri","description":"Stripe billing portal URL (only for paid keys with a Stripe customer)"},"message":{"type":"string"}}}}}}}},"post":{"summary":"Start or upgrade a subscription","operationId":"upgradeSubscription","tags":["Account"],"description":"Creates a Stripe checkout session for new subscriptions or returns a billing portal URL for existing subscribers to upgrade.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["plan"],"properties":{"plan":{"type":"string","enum":["basic","pro","scale","enterprise"],"description":"The plan to subscribe to"},"email":{"type":"string","format":"email","description":"Required for demo keys. Paid keys use the email on file."}}}}}},"responses":{"200":{"description":"Checkout or billing portal URL","content":{"application/json":{"schema":{"type":"object","required":["url","message"],"properties":{"url":{"type":"string","format":"uri","description":"Stripe checkout or billing portal URL. Open in a browser to complete."},"message":{"type":"string"}}}}}},"400":{"description":"Invalid plan or missing email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/foods":{"get":{"summary":"Search the food database","operationId":"searchFoods","tags":["Nutrition"],"parameters":[{"name":"query","in":"query","required":true,"schema":{"type":"string"},"description":"Search term (e.g. \"chicken breast\")"},{"name":"source","in":"query","schema":{"type":"string","enum":["usda","openfoodfacts"]},"description":"Filter results to a single data source."},{"name":"usdaOnly","in":"query","schema":{"type":"boolean"},"description":"Convenience alias for source=usda. Returns only USDA FoodData Central entries - ideal when you want clean generic foods without branded packaged-product clutter."},{"name":"country","in":"query","schema":{"type":"string","minLength":2,"maxLength":2},"description":"ISO 3166-1 alpha-2 country code (e.g. \"FR\", \"US\", \"NL\"). Soft-boosts results tagged with this country in the ranking, so Open Food Facts products local to your users surface first. Does not hard-filter - generic USDA and world results still appear. Also accepted as `cc`."},{"name":"per","in":"query","schema":{"type":"string","enum":["100g","100ml"]},"description":"Normalize nutrition values to a per-100g or per-100ml basis instead of the native serving size."},{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1},"description":"Page number"},{"name":"pageSize","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":50},"description":"Results per page"}],"responses":{"200":{"description":"Paginated list of foods with nutrition data","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Food"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}}}}},"/foods/{id}":{"get":{"summary":"Get full nutrition details for a food","operationId":"getFood","tags":["Nutrition"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Food ID or slug"}],"responses":{"200":{"description":"Food with full nutrition data including micronutrients","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Food"}}}}}},"404":{"description":"Food not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/foods/barcode/{upc}":{"get":{"summary":"Look up a food product by barcode","operationId":"getFoodByBarcode","tags":["Nutrition"],"parameters":[{"name":"upc","in":"path","required":true,"schema":{"type":"string"},"description":"UPC or EAN barcode number"},{"name":"country","in":"query","schema":{"type":"string","minLength":2,"maxLength":2},"description":"ISO 3166-1 alpha-2 country code (e.g. \"FR\", \"NL\"). When provided, the Open Food Facts fallback is queried with the country locale so nutrition labels and serving descriptions come back localized where available. Also accepted as `cc`."}],"responses":{"200":{"description":"Food product with nutrition data","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Food"}}}}}},"404":{"description":"Product not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/foods/log/text":{"post":{"summary":"AI food logging from text description","operationId":"analyzeFoodText","tags":["Nutrition"],"description":"Send a text description of a meal and get back identified foods with full nutrition data. Pro plan and above. Uses your nutrition analysis quota.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","description":"Text description of a meal (e.g. \"grilled chicken with rice and broccoli\")","maxLength":2000}}}}}},"responses":{"200":{"description":"Identified foods with nutrition data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodAnalysisResult"}}}},"403":{"description":"Requires Pro plan or higher","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/foods/log/photo":{"post":{"summary":"AI food logging from photo","operationId":"analyzeFoodPhoto","tags":["Nutrition"],"description":"Send a base64-encoded photo of a meal and get back identified foods with full nutrition data. Pro plan and above. Uses your nutrition analysis quota.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["image"],"properties":{"image":{"type":"string","description":"Base64-encoded image of a meal (JPEG/PNG)"},"media_type":{"type":"string","enum":["image/jpeg","image/png","image/webp","image/gif"],"default":"image/jpeg"}}}}}},"responses":{"200":{"description":"Identified foods with nutrition data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodAnalysisResult"}}}},"403":{"description":"Requires Pro plan or higher","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/foods/log/audio":{"post":{"summary":"AI food logging from voice audio","operationId":"analyzeFoodAudio","tags":["Nutrition"],"description":"Send a base64-encoded audio recording of someone describing a meal. The audio is transcribed and then analyzed like a text log; the response includes the transcript alongside identified foods with full nutrition data. Pro plan and above. Uses your nutrition analysis quota.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["audio"],"properties":{"audio":{"type":"string","description":"Base64-encoded audio recording (max 25MB encoded)"},"format":{"type":"string","enum":["mp3","mp4","m4a","wav","webm","ogg","flac"],"default":"mp3"}}}}}},"responses":{"200":{"description":"Transcript plus identified foods with nutrition data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodAnalysisResult"}}}},"403":{"description":"Requires Pro plan or higher","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/foods/glycemic/ingredients":{"post":{"summary":"Glycemic analysis of an ingredient list","operationId":"analyzeIngredientsGlycemic","tags":["Nutrition"],"description":"Send the ingredients of one meal, with weights where you know them, and get a glycemic index per ingredient, a glycemic load computed from the matched catalog carbohydrate and fiber figures, meal-level totals, and advice for lowering the load. Same response shape as the photo endpoint, without the identification step. Max 30 items. Pro plan and above, and the key must have accepted the glycemic data waiver (POST /foods/glycemic/waiver) or the endpoint returns 403 glycemic_waiver_required. Uses your nutrition analysis quota.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","maxItems":30,"items":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Ingredient name, USDA-style naming matches best"},"grams":{"type":"number","description":"Portion in grams. Omit and the API estimates a typical portion."}}}}}}}}},"responses":{"200":{"description":"Ingredients with glycemic index and load, meal totals, and advice","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GlycemicAnalysisResult"}}}},"403":{"description":"Requires Pro plan or higher, or the glycemic waiver has not been accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/foods/glycemic/waiver":{"get":{"summary":"Read the glycemic data waiver and this key's acceptance status","operationId":"getGlycemicWaiver","tags":["Nutrition"],"description":"Returns the current waiver version, its full text as clauses and as plain text, and whether this key has accepted it.","responses":{"200":{"description":"Waiver text and acceptance status"}}},"post":{"summary":"Accept the glycemic data waiver for this key","operationId":"acceptGlycemicWaiver","tags":["Nutrition"],"description":"One-time acceptance that unlocks the glycemic endpoints for this key. The name given is recorded with the timestamp and originating IP as the acceptance record. Acceptance is stored against the waiver version, so a substantive change to the text asks every key again.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["accept","accepted_by"],"properties":{"accept":{"type":"boolean","enum":[true]},"accepted_by":{"type":"string","description":"Full name of the person accepting on behalf of the account holder"}}}}}},"responses":{"200":{"description":"Acceptance recorded"},"400":{"description":"accept must be true, or accepted_by missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/foods/glycemic/photo":{"post":{"summary":"Glycemic analysis of a meal photo","operationId":"analyzeMealGlycemic","tags":["Nutrition"],"description":"Send a base64-encoded photo of a meal and get back its estimated ingredients with a glycemic index per ingredient, a glycemic load computed from the matched catalog carbohydrate and fiber figures, a meal-level load and category, and concrete advice for lowering the glycemic impact. Ingredient swaps in the advice are re-costed against the catalog so the projected meal load is grounded in the same data. Pro plan and above, and the key must have accepted the glycemic data waiver (POST /foods/glycemic/waiver) or the endpoint returns 403 glycemic_waiver_required. Uses your nutrition analysis quota.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["image"],"properties":{"image":{"type":"string","description":"Base64-encoded image of a meal (JPEG/PNG)"},"media_type":{"type":"string","enum":["image/jpeg","image/png","image/webp","image/gif"],"default":"image/jpeg"}}}}}},"responses":{"200":{"description":"Ingredients with glycemic index and load, meal totals, and advice","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GlycemicAnalysisResult"}}}},"403":{"description":"Requires Pro plan or higher","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/recipes/search":{"get":{"summary":"Search recipes by diet, cuisine, and macros","operationId":"searchRecipes","tags":["Recipes"],"parameters":[{"name":"query","in":"query","schema":{"type":"string"},"description":"Search term (e.g. \"chicken salad\")"},{"name":"diet","in":"query","schema":{"type":"string","enum":["high_protein","low_carb","keto","vegan","vegetarian","mediterranean","paleo"]},"description":"Filter by diet type"},{"name":"cuisine","in":"query","schema":{"type":"string","enum":["american","mediterranean","asian","mexican","italian","indian","japanese"]},"description":"Filter by cuisine"},{"name":"mealType","in":"query","schema":{"type":"string","enum":["breakfast","lunch","dinner","snack"]},"description":"Filter by meal type"},{"name":"maxCalories","in":"query","schema":{"type":"number"},"description":"Maximum calories per serving"},{"name":"minProtein","in":"query","schema":{"type":"number"},"description":"Minimum protein per serving (grams)"},{"name":"page","in":"query","schema":{"type":"integer","default":1},"description":"Page number"},{"name":"pageSize","in":"query","schema":{"type":"integer","default":20,"maximum":50},"description":"Results per page"}],"responses":{"200":{"description":"Paginated list of recipes","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Recipe"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}}}}},"/recipes/{id}":{"get":{"summary":"Get full recipe with ingredients and nutrition","operationId":"getRecipe","tags":["Recipes"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Recipe ID or slug"}],"responses":{"200":{"description":"Full recipe with ingredients, instructions, and nutrition","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Recipe"}}}}}},"404":{"description":"Recipe not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/recipes/diets":{"get":{"summary":"List available diet types","operationId":"listDietTypes","tags":["Recipes"],"responses":{"200":{"description":"List of diet types","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DietType"}}}}}}}}}},"/recipes/meal-types":{"get":{"summary":"List available meal types","operationId":"listMealTypes","tags":["Recipes"],"responses":{"200":{"description":"List of meal types","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MealType"}}}}}}}}}},"/mealplans/generate":{"get":{"summary":"Generate a personalized meal plan","operationId":"generateMealPlan","tags":["Meal Plans"],"parameters":[{"name":"calories","in":"query","required":true,"schema":{"type":"integer"},"description":"Daily calorie target"},{"name":"diet","in":"query","schema":{"type":"string","enum":["balanced","high_protein","low_carb","keto","vegan","vegetarian","mediterranean","paleo"],"default":"balanced"},"description":"Diet preference"},{"name":"meals","in":"query","schema":{"type":"integer","minimum":3,"maximum":6,"default":3},"description":"Number of meals per day"},{"name":"days","in":"query","schema":{"type":"integer","minimum":1,"maximum":7,"default":1},"description":"Number of days in the plan. For days > 1, see data.days[] for per-day breakdown; data.meals/data.totals are aliased to day 1 for backward compatibility."},{"name":"macroSplit","in":"query","schema":{"type":"string","enum":["balanced","high_protein","low_carb","high_fat"],"default":"balanced"},"description":"Macro distribution preference"}],"responses":{"200":{"description":"Generated meal plan with nutrition breakdown","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MealPlan"}}}}}},"400":{"description":"Missing required parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"tags":[{"name":"Exercises","description":"Browse and search exercise videos. Video URLs are pre-signed and expire after 48 hours."},{"name":"Workouts","description":"Generate structured workouts"},{"name":"Programs","description":"Generate multi-week training programs"},{"name":"Form Analysis","description":"AI-powered exercise form analysis from video"},{"name":"Account","description":"Usage and account management"},{"name":"Nutrition","description":"Search foods, get nutrition data, barcode lookup, and AI food logging"},{"name":"Recipes","description":"Search recipes by diet, cuisine, and macros"},{"name":"Meal Plans","description":"Generate personalized meal plans"}]}