{"openapi":"3.1.0","info":{"title":"BasalFit API","version":"1","description":"Recipes, foods and nutrition from USDA FoodData Central. Reading needs no key; results link back to basalfit.com. Writing uses a basalfit.com session from the website. Terms: https://basalfit.com/legal"},"servers":[{"url":"https://basalfit.com"}],"tags":[{"name":"Recipes"},{"name":"Foods"},{"name":"Nutrition"},{"name":"Contributing"},{"name":"Moderation"},{"name":"Service"}],"paths":{"/api/v1/recipes":{"get":{"tags":["Recipes"],"summary":"Search recipes in plain language or by filters","description":"One result per dish (family), best-matching version first. Filters combine with `q`, which the Ask bar parser reads (\"vegan, no peanuts, under 600 kcal\").","parameters":[{"name":"q","in":"query","description":"Plain-language query","schema":{"type":"string"},"example":"vegan, no peanuts, under 600 kcal"},{"name":"diets","in":"query","description":"Comma-separated diets, e.g. vegan,gluten_free","schema":{"type":"string"},"example":"vegetarian"},{"name":"exclude","in":"query","description":"Comma-separated ingredients or allergens to leave out","schema":{"type":"string"},"example":"peanuts"},{"name":"have","in":"query","description":"Comma-separated ingredients every result uses","schema":{"type":"string"},"example":"spinach"},{"name":"max_kcal","in":"query","description":"Most calories per serving","schema":{"type":"number"},"example":700},{"name":"min_protein_g","in":"query","description":"Least protein per serving, in grams","schema":{"type":"number"},"example":10},{"name":"max_minutes","in":"query","description":"Most total minutes","schema":{"type":"number"},"example":60},{"name":"cuisine","in":"query","description":"A cuisine","schema":{"type":"string"},"example":"Indian"},{"name":"limit","in":"query","description":"Results, 1-50","schema":{"type":"integer"},"example":10}],"responses":{"200":{"description":"Matching recipes, with a USDA citation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeSearch"}}}}}},"post":{"tags":["Contributing"],"summary":"Submit a recipe","description":"Saved for review (trusted contributors publish immediately). Ingredients are by id, or by name when BasalFit doesn't list them yet. A near-copy of an existing recipe returns 409 unless `confirm` is \"duplicate\". 10 a day per person.","security":[{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Submission"}}}},"responses":{"201":{"description":"Saved: its slug and how it relates to existing dishes"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"401":{"description":"Sign in first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"403":{"description":"Not allowed, or not from basalfit.com","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"409":{"description":"Looks like an existing recipe"},"422":{"description":"Unknown ingredient or unconvertible unit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"429":{"description":"Daily limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}}}}},"/api/v1/pantry":{"get":{"tags":["Recipes"],"summary":"What can I make? Recipes from what you have","description":"Reads `have` like the Ask bar (\"I have chicken, rice and spinach\"). Amounts are weighed with USDA portions, and a recipe needing more than you have lists it as short; an ingredient without an amount counts as enough. Staples (salt, oil, water) count as on hand. Fewest missing ingredients first, then the largest share on hand; one result per dish.","parameters":[{"name":"have","in":"query","description":"What you have, in plain words, optionally with amounts","schema":{"type":"string"},"example":"2 cups rice, 500 g chicken, spinach"},{"name":"max_missing","in":"query","description":"Most missing ingredients, 0-3 (default 1)","schema":{"type":"integer"},"example":1},{"name":"limit","in":"query","description":"Results, 1-50","schema":{"type":"integer"},"example":10}],"responses":{"200":{"description":"Recognized ingredients (with grams when an amount was given), words not recognized, staples, and recipes with what each is missing or short of"}}}},"/api/v1/recipes/{slug}":{"get":{"tags":["Recipes"],"summary":"Get a recipe","description":"Ingredients with their USDA sources, steps, per-serving label nutrition, diets, and every nutrient per serving.","parameters":[{"name":"slug","in":"path","required":true,"description":"The recipe's slug","schema":{"type":"string"},"example":"garlic-chicken-spinach-rice-bowl"}],"responses":{"200":{"description":"The recipe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipe"}}}},"404":{"description":"Not found"}}},"put":{"tags":["Contributing"],"summary":"Edit a recipe (its author or a moderator)","description":"Same body as a submission. A moderator's edit keeps the status; a trusted author's edit of a published recipe stays published; other edits go back to review.","security":[{"session":[]}],"parameters":[{"name":"slug","in":"path","required":true,"description":"The recipe's slug","schema":{"type":"string"},"example":"garlic-chicken-spinach-rice-bowl"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Submission"}}}},"responses":{"200":{"description":"Saved, with the recipe's new status"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"401":{"description":"Sign in first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"403":{"description":"Not allowed, or not from basalfit.com","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"404":{"description":"Not found"}}}},"/api/v1/nutrition":{"post":{"tags":["Nutrition"],"summary":"Nutrition for any ingredient list","description":"FDA-rounded label values, % Daily Value and diets, from USDA data.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NutritionRequest"}}}},"responses":{"200":{"description":"Per-serving nutrition"},"400":{"description":"Invalid lines"},"422":{"description":"Unknown ingredient"}}}},"/api/v1/foods":{"get":{"tags":["Foods"],"summary":"Search USDA foods by name","description":"Common forms first; a misspelled word is corrected when nothing matches, and `corrected` says what was searched.","parameters":[{"name":"q","in":"query","description":"A food name","schema":{"type":"string"},"example":"greek yogurt"},{"name":"limit","in":"query","description":"Results, 1-50","schema":{"type":"integer"},"example":5}],"responses":{"200":{"description":"Matching foods with per-100 g macros","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodSearch"}}}}}}},"/api/v1/foods/{id}":{"get":{"tags":["Foods"],"summary":"Get one food: every nutrient, portions and ranks","parameters":[{"name":"id","in":"path","required":true,"description":"A USDA FoodData Central id, as returned by food search","schema":{"type":"integer"},"example":170894}],"responses":{"200":{"description":"The food"},"404":{"description":"Not found"}}}},"/api/v1/foods/barcode/{code}":{"get":{"tags":["Foods"],"summary":"Get a packaged food by its barcode","description":"UPC-A, UPC-E, EAN-8, EAN-13 or GTIN-14, with or without spaces. The label's nutrients per 100 g and its serving, from USDA Branded Foods (with ingredients), then products BasalFit members added and a moderator approved, then Open Food Facts (ODbL; records whose numbers don't add up are left out). `source` names which, with `citation` for USDA and Open Food Facts. 400 when the code isn't a valid barcode; 404 when no source has it.","parameters":[{"name":"code","in":"path","required":true,"description":"The digits under the barcode","schema":{"type":"string"},"example":"016000275287"}],"responses":{"200":{"description":"The product"},"400":{"description":"Not a valid barcode"},"404":{"description":"Not in USDA's list"}}}},"/api/v1/foods/compare":{"get":{"tags":["Foods"],"summary":"Compare two foods, every nutrient per 100 g","parameters":[{"name":"a","in":"query","required":true,"description":"A USDA FoodData Central id, as returned by food search","schema":{"type":"integer"},"example":170894},{"name":"b","in":"query","required":true,"description":"A USDA FoodData Central id, as returned by food search","schema":{"type":"integer"},"example":171265}],"responses":{"200":{"description":"Side by side, with which has more"},"404":{"description":"A food wasn't found"}}}},"/api/v1/ingredients/names.json":{"get":{"tags":["Foods"],"summary":"Ingredient names in other languages and scripts","description":"A map from ingredient id to its names, from Wikidata, for reading recipes not written in English.","responses":{"200":{"description":"Names by ingredient id"}}}},"/api/health":{"get":{"tags":["Service"],"summary":"Health of the service and its database and storage","description":"`build` names the build serving, so a deploy can be checked to have gone live.","responses":{"200":{"description":"Healthy"},"503":{"description":"A dependency is down"}}}},"/mcp":{"get":{"tags":["Service"],"summary":"Model Context Protocol server: not used with GET","description":"Remote MCP over Streamable HTTP, no authentication. Tools: search_recipes, get_recipe, search_foods, get_food, nutrition_for. See https://basalfit.com/ai.","responses":{"405":{"description":"Use POST: the server is stateless, with no event stream"}}},"post":{"tags":["Service"],"summary":"MCP JSON-RPC requests","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"JSON-RPC response"},"202":{"description":"Notification accepted"}}}},"/api/v1/products":{"post":{"tags":["Contributing"],"summary":"Add a packaged food from its Nutrition Facts panel","description":"For barcodes USDA doesn't list. Values are per serving, as on the label; they're checked (parts within totals, calories near protein, carbohydrate and fat) and kept per 100 g. Pending until a moderator approves; the response includes the product, shaped like a food, so the submitter can log it now. 20 a day per person.","security":[{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["barcode","name","serving_g","per_serving"],"properties":{"barcode":{"type":"string","example":"096619872596"},"name":{"type":"string","maxLength":120},"brand":{"type":"string","maxLength":80},"serving_g":{"type":"number","minimum":1,"maximum":2000},"serving_text":{"type":"string","maxLength":60,"example":"1 bar"},"per_serving":{"type":"object","description":"kcal, fat_g, carb_g and protein_g required; sat_fat_g, trans_fat_g, sodium_mg, fiber_g, sugar_g, added_sugar_g, vit_d_mcg, calcium_mg, iron_mg, potassium_mg optional","additionalProperties":{"type":"number","minimum":0}}}}}}},"responses":{"201":{"description":"Added for review"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"401":{"description":"Sign in first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"403":{"description":"Not allowed, or not from basalfit.com","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"409":{"description":"USDA lists it, or it was already added"},"429":{"description":"Daily limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}}}}},"/api/v1/moderation/products/{gtin}":{"post":{"tags":["Moderation"],"summary":"Approve or reject an added product (moderators only)","security":[{"session":[]}],"parameters":[{"name":"gtin","in":"path","required":true,"description":"The product's 14-digit barcode, from the queue","schema":{"type":"string"},"example":"00096619872596"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"enum":["approve","reject"]}}}}}},"responses":{"200":{"description":"Reviewed"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"401":{"description":"Sign in first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"403":{"description":"Not allowed, or not from basalfit.com","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"404":{"description":"No pending product with that barcode"}}}},"/api/v1/recipes/{slug}/editable":{"get":{"tags":["Contributing"],"summary":"Get a recipe as its author or a moderator edits it","security":[{"session":[]}],"parameters":[{"name":"slug","in":"path","required":true,"description":"The recipe's slug","schema":{"type":"string"},"example":"garlic-chicken-spinach-rice-bowl"}],"responses":{"200":{"description":"Every line by ingredient id or unlisted name"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"401":{"description":"Sign in first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"403":{"description":"Not allowed, or not from basalfit.com","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"404":{"description":"Not found"}}}},"/api/v1/recipes/{slug}/reports":{"post":{"tags":["Contributing"],"summary":"Report a problem with a recipe","description":"One report per person per recipe; three open reports from different people hide it until a moderator reviews it.","security":[{"session":[]}],"parameters":[{"name":"slug","in":"path","required":true,"description":"The recipe's slug","schema":{"type":"string"},"example":"garlic-chicken-spinach-rice-bowl"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"enum":["wrong_nutrition","unsafe","copied","spam","other"]},"note":{"type":"string","maxLength":300}}}}}},"responses":{"201":{"description":"Recorded; `hidden` says whether this report hid the recipe"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"401":{"description":"Sign in first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"403":{"description":"Not allowed, or not from basalfit.com","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"404":{"description":"Not found"}}}},"/api/v1/moderation/queue":{"get":{"tags":["Moderation"],"summary":"What moderators review (moderators only)","security":[{"session":[]}],"responses":{"200":{"description":"Pending, reported and unlisted-line recipes"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"401":{"description":"Sign in first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"403":{"description":"Not allowed, or not from basalfit.com","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}}}}},"/api/v1/moderation/recipes/{id}":{"post":{"tags":["Moderation"],"summary":"Approve, reject, restore, edit or map a line (moderators only)","security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The recipe id from the queue","schema":{"type":"integer"},"example":1}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"enum":["approve","reject","restore","map","edit"]},"reason":{"type":"string","maxLength":300},"position":{"type":"integer"},"ingredient":{"type":"string"}}}}}},"responses":{"200":{"description":"Done"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"401":{"description":"Sign in first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"403":{"description":"Not allowed, or not from basalfit.com","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"404":{"description":"No such recipe or line"}}}}},"components":{"securitySchemes":{"session":{"type":"apiKey","in":"cookie","name":"__Host-session","description":"Set by signing in on basalfit.com; requests must come from basalfit.com (Origin header)."}},"schemas":{"Errors":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"string"}}}},"RecipeSearch":{"type":"object","properties":{"query":{"type":"string"},"understood":{"type":"array","items":{"type":"string"}},"results":{"type":"array","items":{"type":"object"}},"citation":{"type":"string"}}},"Recipe":{"type":"object","additionalProperties":true},"FoodSearch":{"type":"object","properties":{"query":{"type":"string"},"corrected":{"type":["string","null"]},"results":{"type":"array","items":{"type":"object"}}}},"Submission":{"type":"object","required":["title","servings","prep_min","cook_min","meal_types","ingredients","steps"],"properties":{"title":{"type":"string","minLength":3,"maxLength":120},"summary":{"type":"string","maxLength":400},"servings":{"type":"integer","minimum":1,"maximum":24},"prep_min":{"type":"integer","minimum":0},"cook_min":{"type":"integer","minimum":0},"meal_types":{"type":"array","items":{"enum":["breakfast","lunch","dinner","snack","dessert"]}},"ingredients":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"object","required":["quantity","unit"],"properties":{"ingredient":{"type":"string","description":"A BasalFit ingredient id"},"name":{"type":"string","description":"Or a name BasalFit doesn't list yet"},"quantity":{"type":"number","exclusiveMinimum":0},"unit":{"type":"string"},"note":{"type":"string"},"optional":{"type":"boolean"}}}},"steps":{"type":"array","minItems":1,"maxItems":40,"items":{"type":"string"}},"confirm":{"const":"duplicate"}}},"NutritionRequest":{"type":"object","required":["ingredients"],"properties":{"ingredients":{"type":"array","items":{"type":"object","required":["name","quantity","unit"],"properties":{"name":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"}}}},"servings":{"type":"integer","minimum":1}}}}}}