Foodashi API
The complete recipe API for modern food applications. Search by flavor, nutrient, or ingredient with vector-powered semantic search.
# Get your first recipes curl -X GET "https://api.foodashi.com/api/recipes?cuisine=italian&limit=5" \ -H "X-Api-Key: YOUR_API_KEY"
Authentication
All API requests require authentication via the X-Api-Key header.
You can register a new API key at our developer portal.
Security Best Practices
Your API keys carry many privileges, so be sure to keep them secure! Do not share your secret API keys in publicly accessible areas such as GitHub, client-side code, or blog posts.
X-Api-Key: rcpt_indie_xK9d8sL2mN...
Authorization: Bearer <your API key> is accepted as an alternative to X-Api-Key, for clients and agent frameworks that can only send a Bearer token.
Both headers pass the API's CORS preflight from an allowed origin.
Tier Access
Endpoints are gated by your subscription tier. If you call an endpoint above your plan, you'll receive a 403 response with upgrade details.
{
"error": "Upgrade required",
"code": "tier_required",
"message": "This endpoint requires the pro plan or higher. Your current plan: hobby.",
"current_tier": "hobby",
"required_tier": "pro",
"upgrade_url": "https://foodashi.com/pricing"
}
Plans & Rate Limits
Rate limits and endpoint access are tiered by subscription plan. Usage is counted in credits: most calls cost 1 credit, AI calls cost more (up to 400); every endpoint below shows its cost. Included credits reset on the 1st of each month at 00:00 UTC; the extra usage balance is prepaid and does not reset.
| Plan | Price | Monthly Credits | RPM | API Keys |
|---|---|---|---|---|
| Hobby | Free | 10,000 | 30 | 1 |
| Indie | $25/mo · $250/yr | 150,000 | 600 | 3 |
| Pro | $90/mo · $900/yr | 1,000,000 | 3,000 | 10 |
| Enterprise | Custom | 10,000,000+ | 10,000+ | Unlimited |
Yearly billing = 10× monthly (two months free). Extra usage: $0.50 per 1,000 credits on Indie and $0.30 on Pro, from a prepaid balance; Hobby has none. RPM counts requests, whatever each one costs.
Key lookups. Apart from your plan’s RPM, one client address can make 120 API key lookups per minute. A key used in the last few seconds is not looked up again, so a working integration rarely meets this limit; unknown or invalid keys do count. Over it the answer is 429 rate_limited with "limit": 120, retry_after and a Retry-After header in seconds, and without the X-RateLimit-* headers.
What each tier unlocks
Hobby
25 endpoints: core browsing & reference.
- • Recipe browse (by cuisine, diet, meal-type, time, difficulty)
- • Ingredient search & food groups
- • Nutrient & daily-value reference
- • Utilities (convert, allergens, diets, cuisines)
- • Requires a visible Powered by Foodashi credit
Indie
+ all Hobby54 endpoints: search & matching.
- • Smart (semantic) recipe search & autocomplete
- • Bulk lookup & similar recipes
- • Browse by cooking method / equipment / occasion / season
- • Recipes by ingredients (what's in my fridge)
- • Recipe nutrition / allergens / beverages / scale
- • NutriMetric scoring + FDA/EU nutrition labels
- • Ingredient details, substitutes, portions, conversions
- • Meal plan templates & basic shopping lists
Pro
+ AIAll 74 endpoints: everything + AI.
- • AI meal plan generation + customization
- • Food photo recognition (Vision): dish + ingredient detection
- • Recipe analysis from text or URL
- • Semantic vector search (ingredients)
- • AI beverage pairing (sommelier)
- • Recipe compare + taste / sustainability / medical profiles
- • AI-optimized shopping lists
403 with required_tier and upgrade_url fields so your app can handle upgrades gracefully.
Attribution
On the Hobby plan, wherever your app or site shows Foodashi data, it must show a visible “Powered by Foodashi” credit that links to https://foodashi.com. Indie, Pro and Enterprise plans need no Foodashi credit: you can present the data entirely under your own brand.
Visible means a reader can see and follow it without opening a menu: next to the data, or in the footer of the same page or screen. In a native app, a text button that opens https://foodashi.com in the browser does the same job. Copy this snippet:
<p class="foodashi-credit"><a href="https://foodashi.com">Powered by Foodashi</a></p>
Style it to fit your design, as long as the text stays readable and the link works. This credit is separate from the open-database credits that apply on every plan: if you show nutrition, ingredient or barcode data, also link to foodashi.com/data-sources somewhere your users can find it. The full terms are in the API License Agreement, Section 6.
Error Handling
| Status | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad Request: invalid parameters |
| 401 | Unauthorized: missing or invalid API key |
| 402 | Payment Required, on the AI endpoints only: "limit_type": "ai_spend". This key reached its monthly AI processing limit; every non-AI endpoint keeps working. Write to [email protected] to raise it. It costs nothing. |
| 403 | Forbidden: plan does not include this endpoint (code tier_required), account suspended (account_suspended) or key over the plan key count (key_limit_exceeded) |
| 404 | Not Found: the endpoint or resource does not exist |
| 429 | Too Many Requests: code rate_limited (per-minute; Retry-After in seconds), quota_exceeded or overage_depleted (monthly allowance; Retry-After = seconds to the reset on the 1st, 00:00 UTC), trial_exhausted. rate_limited is your plan’s per-minute limit, or too many API key lookups from one client address (120 per minute, see rate limits), or an AI endpoint’s own per-minute cap, the same on every plan: meal plan generation 5 a minute; meal plan customize, text and URL analysis, shopping list optimize, image and ingredient recognition, cooking tips and meal logging 10 a minute; beverage pairing 15 a minute. overage_depleted means your prepaid extra usage cannot pay for the call; a balance check that did not answer is a 503, never this. While an automatic top-up is on its way it comes with Retry-After: 60: retry in a minute, the credit is usually there. overage_limit_reached means the monthly extra usage spending limit you set in the console is reached; your balance is kept, and raising the limit takes effect within a minute. |
| 413 | Payload Too Large: code payload_too_large. A body over 1 MB is refused (6 MB for image recognition and beverage pairing); some endpoints set a smaller limit and name it in limit_bytes, beside received_bytes. |
| 500 | Server Error: something went wrong on our end |
| 503 | Service Unavailable: code service_unavailable with "retryable": true. Your account’s quota or extra-usage check, or a read of the data behind the answer, did not answer: nothing partial was served, so send the same request again after a short wait. It is never a quota verdict (that is a 429). On the AI endpoints a 503 can also mean the AI step or the AI usage check was unavailable; retry in a minute. A barcode lookup can also answer upstream_rate_limited (with Retry-After) or upstream_unavailable. |
A call that fails costs nothing. An answer of 400, 402, 403, 404, 405, 413, 422 or any 5xx gives back the requests it counted, and any extra usage it took from your balance. There are two exceptions: an AI endpoint whose AI step already ran before the error, and nutrition calculate answering 422 after it matched your ingredients (no ingredient could be matched, or nutrition could not be computed). That work is done, so the call’s credits stay spent; a 400 before any work is still free. A 429 costs nothing either.
{
"error": "Rate Limit Exceeded",
"code": "quota_exceeded",
"message": "Monthly Quota Exceeded",
"retry_after": 561600,
"upgrade_url": "https://foodashi.com/pricing"
}
Every error body carries a stable machine-readable code (for example invalid_parameter, unknown_parameter, unknown_field, invalid_allergen, invalid_id, offset_too_deep, not_found, unauthorized, missing_api_key, invalid_api_key, service_unavailable). Branch on code; the error and message text may change.
Report a Recipe has its own codes: invalid_report, customer_key_required, recipe_not_found and report_limit_exceeded.
Authorization: Bearer <your API key> is accepted as an alternative to X-Api-Key; both pass the CORS preflight from an allowed origin.
/api/recipes
Hobby+
1 credit
Get Recipes
Retrieve a list of recipes with powerful filtering options. Supports pagination, dietary filters, cuisine selection, and detailed metadata filtering.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| page | integer | Page number for pagination, 1-based (default: 1). A value below 1 or not a number is served as page 1. Ignored when random=true. |
| limit | integer | Results per page (default: 20, max: 100). A value below 1 or not a number is served as 20, a value above 100 as 100. |
| offset | integer | Row offset (alternative to page). |
| random | boolean | Return a shuffled sample instead of stable pagination. Aliases: sort=random, shuffle=true. See browse conventions. |
| cuisine | string |
Filter by cuisine. Title-case match (case-insensitive accepted).
Browse all 81 supported cuisines ▾🥡 East / Southeast Asia
JapaneseChineseKoreanThaiVietnameseIndonesianMalaysianFilipinoSingaporeanBurmeseCambodianLaotian
🍛 South / Central Asia
IndianPakistaniSri LankanBangladeshiNepaleseTibetanAfghan
🥙 Middle East
TurkishLebanesePersianSyrianIraqiIsraeliSaudi ArabianYemeniArmenian
🌶️ North Africa
MoroccanTunisianEgyptianAlgerian
🥖 Europe
ItalianFrenchSpanishGreekPortugueseGermanBritishIrishPolishRussianUkrainianHungarianGeorgianAustrianBelgianCzechSwiss
❄️ Nordic
DanishSwedishNorwegianFinnishIcelandic
🌎 Americas
AmericanMexicanBrazilianPeruvianArgentineColombianVenezuelanChileanBolivianEcuadorian
🏝️ Caribbean
CubanJamaicanHaitianDominicanPuerto RicanTrinidadianBarbadian
🌺 Pacific
HawaiianAustralian
🥘 Sub-Saharan Africa
EthiopianNigerianSouth AfricanGhanaianSenegaleseTanzanianKenyanBeninese
🌐 Catch-all
Global
|
| diet | string | Dietary tag, case-insensitive; CSV means all listed; unknown value returns 400 with valid_diets. |
| exclude_alg | string | Comma-separated allergens to exclude. Case-insensitive EU-14 names: Gluten, Crustaceans, Eggs, Fish, Peanuts, Soy, Dairy, Tree Nuts, Celery, Mustard, Sesame, Sulfites, Lupin, Molluscs (synonyms Soybeans, Milk, Wheat, Nuts, Sulphites accepted). Aliases: allergen, exclude_allergens. "Shellfish" is ambiguous: use Crustaceans and/or Molluscs. Any unknown name returns 400 invalid_allergen. |
| meal_type | string |
Filter by when/how it's eaten. Recipes carry 1-5 values (multi-value array); matches if any value matches.
Browse all 16 meal_type values ▾🕐 Time of day
BreakfastBrunchLunchDinnerLate Night
🍽️ Course position
AppetizerSideMain CourseDessert
☕ Between-meal & context
SnackTea TimeAperitifDrinkStreet Food
🧂 Non-meal accessory
CondimentComponent
|
| dish_type | string |
Filter by structural identity: what the dish IS in form. Recipes carry 1-3 values (multi-value array); matches if any value matches.
Browse all 108 dish_type values ▾💧 Liquid
SoupStewCurryPorridge
🌯 Hand-held SandwichWrapBurgerHot DogSpring Roll 🍝 Pasta & Noodle PastaNoodle DishStuffed Pasta 🍚 Rice Rice DishFried RiceSushi 🌾 Grain Grain Dish 🥗 Salad & Raw SaladSlawCevicheTartare 🔥 Cooking method Stir-FryGrilled DishRoastBraised DishSteamed DishBarbecueSkewerFried Dish 🍳 Fritter family FritterCutletCroquette 🥘 Baked savory CasseroleGratinQuicheSavory PieSoufflé 🥟 Filled dough DumplingEmpanadaHand Pie 🍕 Pizza & Flatbread PizzaCalzoneStuffed Flatbread 🍞 Bread BreadFlatbreadCornbread 🥞 Pancake PancakeCrepeWaffle 🥚 Egg OmeletScrambled EggsEgg Dish 🫘 Legume Bean DishLentil DishTofu Dish 🥕 Vegetable Vegetable DishStuffed Vegetable 🧀 Cheese FondueCheese Plate 🧂 Cured & Sausage SausageCharcuterieCured FishJerky 🥩 Meat SteakRoast MeatRibsMeatballGround Meat Dish 🐠 Seafood Fish FilletWhole FishShellfish 🍰 Sweet families CakeCheesecakeCookieSweet PiePastryPuddingCustardFrozenConfectionCandyJellyAssembledCrumble 🥛 Yogurt Yogurt Dish 🥄 Sauce & Condiment SauceDressingMarinadeDipSpreadSpice Paste 🥒 Pickle & Preserve PickleJamCompote 🥤 Beverage (use with meal_type=Drink) SmoothieMilkshakeJuiceCocktailMocktailTeaCoffeeHot ChocolateFermented Beverage 🥨 Pantry & Snack CrackerGranolaEnergy BarPopcorn 🍱 Plate format Mixed PlateBowl |
| difficulty | string | Filter by difficulty: "Easy", "Hard" or "Expert", case-insensitive. "Medium" is not accepted as a filter value and is refused with 400 invalid_parameter (what these fields mean). |
| equipment | string | Filter by required equipment (e.g., "Blender", "Air Fryer") |
| tag | string | Filter by specific custom tags (e.g., "Authentic", "Spicy") |
| time | integer | Maximum cook time in minutes (cook_time_minutes) |
| total_time | integer | Maximum total time (prep + cook + rest) in minutes |
| min_nutrimetric | integer | Minimum NutriMetric score (0-100) |
| cost_tier | string | Filter by estimated cost ("cheap", "moderate", "expensive") |
| search | string | Text search in recipe titles (alias: q) |
| fields | CSV | Return only these top-level recipe fields (id, ai_generated and api_version always come). See Trimming the Recipe. |
Unknown query parameters return 400 with code unknown_parameter and the list of accepted_parameters. Out-of-range page and limit values are clamped, never refused. A page past the last one returns 200 with an empty recipes list and the true pagination.total and total_pages. Pages past offset 1000 return 400 with code offset_too_deep; pagination.total_pages counts only servable pages (pages_truncated tells you when more match).
curl -X GET "https://api.foodashi.com/api/recipes?cuisine=Mexican&exclude_alg=Peanuts&limit=2" \ -H "X-Api-Key: YOUR_API_KEY"
Example Response
{
"recipes": [
{
"id": "a1b2c3d4-...",
"title": "Spaghetti Carbonara",
"cuisine": "Italian",
"meal_types": ["Dinner", "Main Course"],
"dish_types": ["Pasta"],
"difficulty": null,
"total_time_minutes": 30,
"servings": 4,
"image_url": "https://...",
"dietary_tags": ["Nut-Free"],
"allergens": ["Gluten", "Eggs", "Dairy"],
"nutrition": { "calories": 520, "protein_g": 22, ... },
"ingredients": [ ... ],
"steps": [ ... ],
...
}
],
"pagination": { "page": 1, "limit": 2, "total": 86, "total_pages": 43 }
}
Abridged: each item is the full recipe object, every field listed in the Response Schema.
/api/recipes/details
Hobby+
1 credit
Recipe Details
Get complete details for a single recipe including full ingredients, step-by-step instructions, nutrition info, taste profile, and storage metadata.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| id Required | string (UUID)Required | The recipe ID, a UUID (e.g. b1a2c3d4-e5f6-7890-abcd-ef1234567890) |
| fields | CSV | Return only these top-level recipe fields (id, ai_generated and api_version always come). See Trimming the Recipe. |
curl -X GET "https://api.foodashi.com/api/recipes/details?id=b1a2c3d4-e5f6-7890-abcd-ef1234567890" \ -H "X-Api-Key: YOUR_API_KEY"
Example Response
{
"id": "07211f3f-a2e6-4d5e-ad3c-703ac6512548",
"title": "Milanesa de Pollo Napolitana",
"title_english": "Chicken Neapolitan Milanese",
"cuisine": "Argentine",
"cuisine_code": "AR",
"servings": 4,
"ingredients": [
{ "text": "600 g chicken breast", "name": "chicken breast", "quantity": 600, "unit": "g", "gram_weight": 600, "is_main": true, ... }
],
"steps": [
{ "step": 1, "text": "Place each chicken breast cutlet between two sheets...", "duration_minutes": 5, ... }
],
"nutrition": {
"calories": 758,
"protein_g": 58.4,
"fat_g": 45.5,
"carbohydrate_g": 28.1,
...
"per_100g": { "calories": 222.8, "protein_g": 17.2, ... }
},
"nutrimetric": {
"grade": "C",
"score": 69,
"label": "Moderate",
"version": "nutrimetric-1.0"
},
...
}
Abridged: the answer is the full recipe object, every field listed in the Response Schema.
nutrimetric and beverage_pairing are paid fields (Indie plan and above). A Hobby key receives a {"locked": true, ...} marker in their place; see Paid Fields.
/api/random
Hobby+
1 credit
Random Recipe
Returns a random recipe, great for "Surprise Me" features. Every filter is optional and they combine.
| Parameter | Type | Description |
|---|---|---|
| tags | string | CSV; each token must match a meal type (dinner), dish type (soup) or recipe tag |
| cuisine | string | Cuisine name, case-insensitive (e.g. Italian) |
| diet | string | Dietary tag, case-insensitive; CSV means all listed; unknown value returns 400 with valid_diets |
| meal_type | string | Meal type (e.g. Dinner) |
| dish_type | string | Dish type (e.g. Soup) |
| exclude_allergens | string | CSV of EU-14 allergens to exclude, case-insensitive (aliases allergen, exclude_alg); an unknown name returns 400 invalid_allergen |
| count | integer | 1 to 10, default 1. Clamped, never refused: a value below 1 or not a number returns 1 recipe, a value above 10 returns 10. |
| fields | CSV | Return only these top-level recipe fields (id, ai_generated and api_version always come). See Trimming the Recipe. |
curl -X GET "https://api.foodashi.com/api/random?tags=dinner" \ -H "X-Api-Key: YOUR_API_KEY"
count=1 returns one recipe object; count>1 returns {recipes, count} (plus partial and failed_ids if a recipe could not be formatted).
Browse conventions (read once, applies everywhere)
Every /api/recipes/by-* endpoint (plus /api/recipes, /api/recipes/filter, /api/ingredients/by-food-group, and /api/ingredients/:id/recipes) supports the same set of query conventions. Learn these once, use them everywhere.
Every list returns the full recipe
Each item of recipes[] is the complete recipe object /api/recipes/details returns, ingredients, steps, nutrition and allergens included, under your plan. Every browse endpoint and /api/recipes/filter also take fields to return only the fields you name (Trimming the Recipe). A list may carry "partial": true and failed_ids when a recipe could not be formatted, and a list whose data could not be read is a retryable 503 service_unavailable, never a partial page.
Pagination (default behaviour)
| Parameter | Type | Description |
|---|---|---|
| page | integer | Page number, 1-based (default: 1). A value below 1 or not a number is served as page 1. |
| limit | integer | Results per page (default: 20, max: 100; max 50 on /api/ingredients/:id/recipes). A value below 1 or not a number is served as 20, a value above the max as the max. |
| offset | integer | Row offset; wins over page. |
A page past the last one: 200 with an empty list and the true pagination.total and total_pages. | ||
Past offset 1000: 400 with code offset_too_deep. | ||
Default ordering: newest-published first. Every response includes a pagination object with page, limit, total, and total_pages, and page and limit are the values actually served after clamping. total_pages counts only the pages that can be served; when more recipes match, pages_truncated is true.
Random mode (new 2026-05-10)
When you want a fresh sample on every request (e.g. "surprise me" / "explore" UI), pass any of:
| Parameter | Value | Notes |
|---|---|---|
| random | true | Most common form. |
| sort | random | Alternative spelling (consistent with the sort param on /api/recipes/filter). |
| shuffle | true | Alternative spelling. |
All three forms are accepted; pick whichever fits your code style. Each call reads a window of matching recipes at a random position in the whole matching set and shuffles it, so every matching recipe can appear. Pagination is meaningless in random mode: each request is its own independent sample.
curl -X GET "https://api.foodashi.com/api/recipes/by-cuisine?cuisine=Italian&random=true&limit=5" \ -H "X-Api-Key: YOUR_API_KEY" # Same call again returns 5 DIFFERENT Italian recipes curl -X GET "https://api.foodashi.com/api/recipes/by-cuisine?cuisine=Italian&random=true&limit=5" \ -H "X-Api-Key: YOUR_API_KEY"
Season and occasion queries match what the recipe is served with
A season or occasion filter returns exactly the recipes whose served seasons or occasions array holds the value you asked for (what these fields mean). An empty array means the value is not known, so such a recipe matches no season and no occasion.
- •
?season=Spring,Summer,Fall(aliasAutumn) orWinter: recipes served with that season. - •
?season=Year-Roundand?occasion=Everydayare refused with400 invalid_parameter, with areasonand thevalid_values. Both were written when no evidence was found, so they are not served. Omit the filter to list every recipe. - • On
/api/recipes/by-seasonand/api/recipes/by-occasion, any other value (?season=Monsoon,?occasion=Birthday) is refused with400 invalid_parameterand the acceptedvalid_values, never an empty page. - •
?occasion=Weeknight: recipes whosetotal_time_minutesis 30 or less, waits included.
This applies to /api/recipes/by-season, /api/recipes/by-occasion and /api/recipes/filter.
String matching: case & plural tolerance
Ingredient-based endpoints (/api/recipes/by-ingredients, /api/ingredients/:id/recipes text fallback) normalise input before matching:
- • Case-insensitive:
red onion=Red Onion=RED ONION - • Whitespace collapsed:
red onion(double space) =red onion - • Plural-tolerant:
onion=onions,tomato=tomatoes,cherry=cherries - • Substring match:
chickenmatches "chicken breast", "ground chicken", etc. Use longer phrases for specificity. - • Typo-intolerant:
red ononreturns 0. Call/api/ingredients/autocompletefirst if you need fuzzy/typo handling.
Taxonomy filters: include & exclude (works on every browse endpoint)
Every /api/recipes/by-* browse endpoint and /api/recipes/filter accept these optional taxonomy filters. They combine with the endpoint’s own filters (diet, exclude_allergens, max_time_minutes, difficulty, has_image, …). All values are case-insensitive, resolved against the two-axis taxonomy, and unknown values are silently ignored (never an error).
| Parameter | Type | Description |
|---|---|---|
| meal_type / meal_types | CSV | Include recipes whose meal_types[] match any listed value. Example: meal_types=Dinner,Lunch. |
| dish_type / dish_types | CSV | Include recipes whose dish_types[] match any listed value. Example: dish_types=Pasta,Pizza. |
| exclude_meal_types | CSV | Drop recipes whose meal_types[] match any listed value. Example: exclude_meal_types=Dessert,Appetizer,Side. |
| exclude_dish_types | CSV | Drop recipes whose dish_types[] match any listed value. Example: exclude_dish_types=Salad,Cake. |
| exclude_diets | CSV | Drop recipes tagged with any listed diet. Example: exclude_diets=Vegan. |
| min_time_minutes / max_time_minutes | integer | Total-time bounds (prep + cook + rest), in minutes. Combine the two for a range. See the time fields for how total, active and rest relate. |
| max_active_minutes | integer | Hands-on time bound: keeps recipes whose active_time_minutes is at most this many minutes, a whole number from 0. Total time counts waits that need no cook (a marinade, a rise, a chill), so “quick to make” is active time: 2,887 of the 7,180 published recipes need 30 minutes or less of hands-on work, while only 485 are done in 30 minutes total. Any other value is a 400 invalid_parameter. |
curl -X GET "https://api.foodashi.com/api/recipes/by-cuisine?cuisine=Italian&exclude_meal_types=Dessert,Appetizer,Side&exclude_dish_types=Salad&limit=100" \ -H "X-Api-Key: YOUR_API_KEY"
Unknown parameters are refused
Every /api/recipes/by-* endpoint answers a parameter name it does not read with 400 and code unknown_parameter: unknown_parameters names it and accepted_parameters lists every name that endpoint reads, so a misspelt filter never returns an unfiltered page that looks filtered. The cache buster _ is accepted.
To exclude allergens on any /api/recipes/by-* endpoint, send exclude_allergens (aliases exclude_alg and allergen) with case-insensitive EU-14 names, as on /api/recipes; an unknown name is a 400 with code invalid_allergen. /api/recipes/filter takes exclude_allergens only: the two aliases are refused there with 400 unknown_parameter. /api/recipes/by-ingredients applies the allergen exclusion and the taxonomy filters above before it counts its total and cuts its page.
/api/recipes/by-cuisine
Hobby+
1 credit
Browse by Cuisine
Browse recipes filtered by cuisine type. Returns full recipe objects with pagination.
| Parameter | Type | Description |
|---|---|---|
| cuisine Required | stringRequired | Cuisine name (e.g., "Italian", "Mexican", "Japanese") |
| page | integer | Page number (default: 1) |
| limit | integer | Results per page (default: 20, max: 100) |
curl -X GET "https://api.foodashi.com/api/recipes/by-cuisine?cuisine=Italian&limit=10" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/by-* browse endpoint) also accepts the optional taxonomy filters: meal_type/meal_types and dish_type/dish_types (CSV, match any), the exclude_meal_types/exclude_dish_types/exclude_diets drop-lists, min_time_minutes/max_time_minutes, the hands-on bound max_active_minutes, and fields. All optional. See browse conventions.
GET /api/recipes/by-cuisine?cuisine=Italian&exclude_meal_types=Dessert,Appetizer,Side&exclude_dish_types=Salad&limit=100→ Italian main dishes, no desserts / starters / salads.
/api/recipes/by-region
Hobby+
1 credit
Browse by Region
Group recipes by sub-continental region (e.g. Southeast Asia covers Thai + Vietnamese + Malaysian + Indonesian + Filipino in one call). Perfect for regional food landing pages.
| Parameter | Type | Description |
|---|---|---|
| regionRequired | string | One of: East Asia, Southeast Asia, South Asia, Central Asia, Middle East, North Africa, Sub-Saharan Africa, Southern Europe, Western Europe, Northern Europe, Eastern Europe, North America, Latin America, Caribbean, Oceania, Global |
| page | integer | Page number (default: 1) |
| limit | integer | Results per page (default: 20, max: 100) |
curl -X GET "https://api.foodashi.com/api/recipes/by-region?region=Southeast%20Asia&limit=20" \ -H "X-Api-Key: YOUR_API_KEY"
Response shape: { recipes: [...], filter: { region }, pagination: { page, limit, total } }
/api/recipes/by-continent
Hobby+
1 credit
Browse by Continent
Broadest geographic filter. Useful for top-level navigation like “Asian cuisine” or “European cuisine”.
| Parameter | Type | Description |
|---|---|---|
| continentRequired | string | One of: Asia, Europe, Africa, Americas, Oceania, Global |
| page | integer | Page number (default: 1) |
| limit | integer | Results per page (default: 20, max: 100) |
curl -X GET "https://api.foodashi.com/api/recipes/by-continent?continent=Asia&limit=30" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/by-country
Hobby+
1 credit
Browse by Country
Filter recipes by a specific country using its ISO-3166-1 alpha-2 code (e.g. JP for Japan, IT for Italy, MX for Mexico). Perfect for country-flag navigation UIs.
| Parameter | Type | Description |
|---|---|---|
| countryRequired | string | ISO-3166-1 alpha-2 country code (case-insensitive). Example: JP, IT, BR, MX, IN, MY. |
| page | integer | Page number (default: 1) |
| limit | integer | Results per page (default: 20, max: 100) |
curl -X GET "https://api.foodashi.com/api/recipes/by-country?country=JP&limit=25" \ -H "X-Api-Key: YOUR_API_KEY"
Response includes filter.cuisines_in_country: the list of cuisines mapped to that country, so you can display them as sub-filters in your UI.
/api/recipes/filter
Indie+
1 credit
Multi-Filter Search
The power user’s endpoint. Combine any subset of the filters below in a single call: location, diet, time, cost, equipment, tags, nutrition, sort. All filters are optional and AND-combined. Empty filter values are ignored.
?season=Spring&dish_type=Salad&max_time_minutes=30&diet=Vegan&min_servings=4
For valid values of each param, jump to the Reference: Valid values section.
Filters: Location
| Parameter | Type | Description |
|---|---|---|
| cuisine | string | Exact cuisine name. See 81 valid cuisines. Example: Italian, Thai, Sri Lankan. |
| region | string | Sub-continental region. See 14 valid regions. Example: Southeast Asia, Southern Europe. |
| continent | string | Africa, Americas, Asia, Europe, or Oceania. |
| country | string | ISO-3166-1 alpha-2 code (alias country_code). Maps to all cuisines for that country. Example: JP, IT, MX. A code with no cuisine returns 400. |
Filters: Type, Style & Difficulty
| Parameter | Type | Description |
|---|---|---|
| meal_type / meal_types | string / CSV | WHEN it’s eaten. See 16 valid meal types. Example: Breakfast, Dinner, Snack. Accepts a comma-separated list (alias meal_types). Matches recipes whose meal_types[] include any listed value. Case-insensitive; unknown values are ignored. Example: meal_types=Dinner,Lunch. |
| dish_type / dish_types | string / CSV | WHAT it structurally is. See 108 valid dish types. Example: Salad, Soup, Curry, Pasta, Cake. Different from meal_type: a Salad can be a Lunch, a Side, or a Main. Accepts a comma-separated list (alias dish_types). Matches recipes whose dish_types[] include any listed value. Case-insensitive; unknown values are ignored. Example: dish_types=Pasta,Pizza. |
| exclude_meal_types | CSV | Comma-separated meal types: drop recipes whose meal_types[] include any listed value. Case-insensitive; unknown values ignored. Example: exclude_meal_types=Dessert,Appetizer,Side. |
| exclude_dish_types | CSV | Comma-separated dish types: drop recipes whose dish_types[] include any listed value. Case-insensitive; unknown values ignored. Example: exclude_dish_types=Salad,Cake. |
| difficulty | string | Easy, Hard or Expert. Medium is not accepted as a filter value and answers 400 invalid_parameter (what these fields mean). |
Filters: Diet, Tags & Allergens
| Parameter | Type | Description |
|---|---|---|
| diet | string | One dietary tag the recipe must satisfy. See 21 dietary tags. Example: Vegan, Gluten-Free. |
| exclude_diets | CSV | Comma-separated dietary tags: drop recipes tagged with any listed diet. Case-insensitive; unknown values ignored. Example: exclude_diets=Vegan. |
| tag | string | Single free-form tag the recipe must have. Example: weeknight-dinner, comfort-food. |
| include_tags | CSV | Comma-separated tags: recipe must contain all of them. Example: comfort-food,weeknight-dinner. |
| exclude_tags | CSV | Comma-separated tags: recipe must contain none of them. |
| exclude_allergens | CSV | EU-14 allergens to filter out. See 14 valid allergens. Example: Eggs,Dairy,Nuts. |
Filters: Time, Servings & Cost
| Parameter | Type | Description |
|---|---|---|
| max_time_minutes | integer | Max total time (prep + cook + rest), in minutes. Because rest is included, a low value correctly excludes dishes needing an overnight marinade. For the hands-on figure, active_time_minutes, use max_active_minutes. |
| min_time_minutes | integer | Min total time. Combine with max_time_minutes for a range. |
| min_servings | integer | Minimum servings (useful for batch cooking). |
| max_servings | integer | Maximum servings. |
| servings | integer | Exact servings. |
| max_prep_minutes | integer | Max prep time, in minutes. |
| max_active_minutes | integer | Max hands-on time (active_time_minutes), a whole number from 0. Total time counts waits that need no cook (a marinade, a rise, a chill), so “quick to make” is active time: 2,887 of the 7,180 published recipes need 30 minutes or less of hands-on work, while only 485 are done in 30 minutes total. Any other value is a 400 invalid_parameter. |
| cost_level_max | integer | 1 (budget) to 4 (luxury). A recipe whose cost level is not known (cost_level: null) never matches. |
Filters: Technique & Context
| Parameter | Type | Description |
|---|---|---|
| cooking_method | string | Single technique. See 97 valid methods. Example: baked, grilled, steamed, stir-fried. |
| equipment | string | Single piece of equipment the recipe uses. Example: wok, cast iron skillet, oven, slow cooker. |
| occasion | string | See 5 valid occasions. Example: Weeknight, Holiday, Party. Everyday is refused (not served). |
| season | string | Spring, Summer, Fall (or alias Autumn) or Winter: the recipes served with that season. Year-Round is refused (not served). See browse conventions. |
Filters: Nutrition & Quality
| Parameter | Type | Description |
|---|---|---|
| min_calories | number | Min kcal per serving. |
| max_calories | number | Max kcal per serving. |
| min_protein_g | number | Min grams of protein per serving. |
| max_protein_g | number | Max grams of protein per serving. |
| min_fat_g | number | Min grams of fat per serving. |
| max_fat_g | number | Max grams of fat per serving. |
| max_saturated_fat_g | number | Max grams of saturated fat per serving. |
| min_carbs_g | number | Min grams of carbohydrate per serving. |
| max_carbs_g | number | Max grams of carbohydrate per serving. |
| min_fiber_g | number | Min grams of fiber per serving. |
| max_sugar_g | number | Max grams of sugars per serving. |
| max_sodium_mg | number | Max milligrams of sodium per serving. |
| nutrimetric_min_grade | string | Foodashi NutriMetric grade gate. A, B, C, D, E or F. Value passes if recipe’s grade is ≥ requested grade (A is best). |
| has_image | boolean | true = only recipes with images; false = only without. |
Sort & Pagination
| Parameter | Type | Description |
|---|---|---|
| sort | string | recent (default, newest first; alias newest), time_asc (fastest first), time_desc, nutrimetric (best score first), or random. |
| random | boolean | Shuffle results. See browse conventions. |
| limit | integer | Results per page (default: 20, max: 100). |
| page | integer | Page number (default: 1). |
| offset | integer | Row offset; wins over page. Past 1000 returns 400 offset_too_deep. |
| fields | CSV | Return only these top-level recipe fields (id, ai_generated and api_version always come). See Trimming the Recipe. |
Nutrient bounds are per serving and compared as numbers. Malformed numbers, an unknown sort, grade or country code and unknown parameters return 400 (code invalid_parameter or unknown_parameter).
Examples
Quick weeknight dinners (under 30 min, Easy)
curl -X GET "https://api.foodashi.com/api/recipes/filter?meal_type=Dinner&max_time_minutes=30&difficulty=Easy&sort=time_asc&limit=50" \ -H "X-Api-Key: YOUR_API_KEY"
Healthy high-protein vegan meals (Grade B+, min 25g protein)
curl -X GET "https://api.foodashi.com/api/recipes/filter?diet=Vegan&min_protein_g=25&nutrimetric_min_grade=B&sort=nutrimetric&limit=30" \ -H "X-Api-Key: YOUR_API_KEY"
Mediterranean cuisine, no eggs or nuts, with image, Grade A nutrition
curl -X GET "https://api.foodashi.com/api/recipes/filter?region=Southern%20Europe&exclude_allergens=Eggs,Nuts&has_image=true&nutrimetric_min_grade=A&limit=100" \ -H "X-Api-Key: YOUR_API_KEY"
Response Shape
{
"recipes": [
{ // the full recipe object, as /api/recipes/details returns it (abridged)
"id", "title", "title_english", "cuisine", "cuisine_code", "image_url",
"total_time_minutes", "active_time_minutes", "meal_types", "dish_types",
"dietary_tags", "allergens", "nutrition", "ingredients", "steps", ... }
],
"filter": { // The parameters applied, echoed back so you can verify your query was parsed correctly
"cuisine": "Italian",
"max_time_minutes": 30,
"sort": "time_asc"
},
"pagination": { "page": 1, "limit": 50, "total": 342, "total_pages": 7 }
}
Performance tips
- • All filters resolve to indexed columns, so even combining 10 filters returns in <200ms.
- • Use
include_tagssparingly; it performs a GIN array-contains scan. Max 10 tags. - •
limit=100is the ceiling. For >100 results, paginate withpage=2, etc. - •
filterin the response echoes the parameters the server applied; a misspelt parameter name returns400unknown_parameterinstead of being ignored. - • Rendering a results grid? Send
fields=title,image_url,total_time_minutes,active_time_minutesfor small pages, then fetch the whole recipe when one is opened.
Reference: Valid values
Every filter param above expects an exact value from one of the catalogues below. Case-sensitive unless noted. Copy-paste straight into your query string.
dish_type: 108 valid values
Pass to dish_type=. Describes what the dish structurally is, unlike meal_type (when it's eaten). A "Salad" can be a Lunch, a Side, or a Main.
SoupStewCurryPorridgeSandwichWrapBurgerHot DogSpring RollPastaNoodle DishStuffed PastaRice DishFried RiceSushiGrain DishSaladSlawCevicheTartareStir-FryGrilled DishRoastBraised DishSteamed DishBarbecueSkewerFried DishFritterCutletCroquetteCasseroleGratinQuicheSavory PieSouffléDumplingEmpanadaHand PiePizzaCalzoneStuffed FlatbreadBreadFlatbreadCornbreadPancakeCrepeWaffleOmeletScrambled EggsEgg DishBean DishLentil DishTofu DishVegetable DishStuffed VegetableFondueCheese PlateSausageCharcuterieCured FishJerkySteakRoast MeatRibsMeatballGround Meat DishFish FilletWhole FishShellfishCakeCheesecakeCookieSweet PiePastryPuddingCustardFrozenConfectionCandyJellyAssembledCrumbleSauceDressingMarinadeDipSpreadSpice PasteYogurt DishPickleJamCompoteSmoothieMilkshakeJuiceCocktailMocktailTeaCoffeeHot ChocolateFermented BeverageCrackerGranolaEnergy BarPopcornMixed PlateBowlmeal_type: 16 valid values
Pass to meal_type=. Describes WHEN it's eaten or its role at the table. Different recipes can share a dish_type but have different meal_types (a Salad can be Lunch, Side, or Main).
BreakfastBrunchLunchDinnerLate NightAppetizerSideMain CourseDessertSnackTea TimeAperitifDrinkStreet FoodCondimentComponentcuisine: 81 valid values
Pass to cuisine=. Case-sensitive exact match.
AustrianBelgianBritishCzechDanishFinnishFrenchGeorgianGermanGreekHungarianIcelandicIrishItalianNorwegianPolishPortugueseRussianSpanishSwedishSwissUkrainianChineseJapaneseKoreanTibetanBurmeseCambodianFilipinoIndonesianLaotianMalaysianSingaporeanThaiVietnameseAfghanBangladeshiIndianNepalesePakistaniSri LankanAlgerianArmenianEgyptianIraqiIsraeliLebaneseMoroccanPersianSaudi ArabianSyrianTunisianTurkishYemeniBenineseEthiopianGhanaianKenyanNigerianSenegaleseSouth AfricanTanzanianAmericanHawaiianMexicanArgentineBarbadianBolivianBrazilianChileanColombianCubanDominicanEcuadorianHaitianJamaicanPeruvianPuerto RicanTrinidadianVenezuelanAustralianregion: 14 valid values
Pass to region=. Sub-continental grouping.
CaribbeanEast AsiaEastern EuropeLatin AmericaMiddle EastNorth AfricaNorth AmericaNorthern EuropeOceaniaSouth AsiaSoutheast AsiaSouthern EuropeSub-Saharan AfricaWestern Europecontinent: 5 valid values
AfricaAmericasAsiaEuropeOceaniadiet: 21 dietary tags
Pass to diet=. Recipe must satisfy this dietary constraint. Note: include_tags / exclude_tags use a different (free-form) tag namespace; this diet param is the curated dietary-restriction list. Low-FODMAP is withheld on every recipe: a low-FODMAP rating depends on the amount of each food per serving and on how FODMAPs add up across the meal, and we hold no sourced per-serving FODMAP data. It stays a valid value, matches no recipe, and the response names the reason in withheld_diets.
VegetarianVeganPescatarianPaleoWhole30KetoLow-CarbHigh-ProteinLow-FatLow-SodiumLow-SugarGluten-FreeDairy-FreeEgg-FreeSoy-FreeNut-FreeShellfish-FreeLow-FODMAPDiabetic-FriendlyHeart-HealthyMediterraneanexclude_allergens: EU-14 list
Pass a CSV like exclude_allergens=Eggs,Dairy,Nuts. Mapped to EU Regulation 1169/2011. Recipes containing any listed allergen are filtered out.
GlutenCrustaceansEggsFishPeanutsSoybeansDairyTree NutsCeleryMustardSesameSulfitesLupinMolluscscooking_method: 97 valid values
Pass to cooking_method= or by-cooking-method. Case does not matter. Single value (intersect with other filters for AND). The everyday word for a method is accepted too: baking for baked, roasting for roasted, grilling for grilled, frying for fried, steaming for steamed, and the like. smoking (cold smoke) and smoked (hot smoke) are two methods. Any other value is refused with 400 and the list.
acid-curingair-dryingbakedbarbecuingbastingbhunablanchedblendingblisteringboiledbraisedbriningbroilingbrowningcandyingcanningcaramelizingcharcoal-grillingchillingchurningclarifyingcookedcrystallizingcuringdeep-frieddeglazingdehydratingdry-frieddry-roastingdryingdum cookingemulsifyingfermentingflambéingfreezingfriedgellingglazinggratinatinggriddle-fryinggriddlinggrilledinfusingmaceratingmarinatingmaturationmeltingno-cookoven dryingoven-roastingpan-friedpan-roastingpan-searingpar-fryingparboilingpicklingpit-roastedpoachedpreservingpressure cookingproofingpureeingreducingrenderingroastedrotisserieroux-makingsauce-makingsauteedscorchingscramblingsearedsettingshallow fryingsimmeredslow cookingslow-fryingslow-roastingsmokedsmokingsoakingsofritospit roastingsteamedsteepingstewingstir-friedstone-cookingstovetop-cookingstuffingsun-dryingtemperingthickeningtoastingwaffle iron cookingwarmingwater-bath canningoccasion: 5 valid values
Pass to occasion=. Case-insensitive. Weeknight means the whole recipe, waits included, takes 30 minutes or less; the others come from the recipe’s own text and tags. A recipe with no known occasion serves occasions: []. Everyday is refused (what these fields mean).
WeeknightFamily MealPartyHolidayMeal Prepseason: 4 valid values
Pass to season=. Case-insensitive. Autumn is accepted as an alias for Fall. A recipe with no known season serves seasons: [], and Year-Round is refused (what these fields mean).
SpringSummerFallWinterequipment: 300+ values (free-form)
Pass to equipment=. The catalog has 300+ distinct equipment names: pass any specific one for an exact match. Common values:
wokovenblenderfood processorgrillgrill pancast iron skilletdutch ovenslow cookerrice cookermortar and pestlestand mixerhand mixersteamertagineclaypotdeep fryermicrowavewaffle ironrolling pinmandolinecountry: ISO 3166-1 alpha-2 codes
Pass an ISO country code to country=. Foodashi maps it to all cuisines with that flag (e.g. US covers American, Hawaiian, Cajun, Creole). Common examples:
JPITFRMXCNINTHVNGRESPTBRARUSGBDEKRTR/api/recipes/by-diet
Hobby+
1 credit
Browse by Diet
Filter recipes by dietary tag. Searches within the recipe's dietary_tags array.
A withheld diet (Low-FODMAP) is a valid value that matches no recipe: the answer is a 200 with no recipes and withheld_diets: [{"diet": "Low-FODMAP", "reason": "..."}]. The same holds for diet= on every endpoint that takes it, and exclude_diets=Low-FODMAP excludes nothing.
| Parameter | Type | Description |
|---|---|---|
| diet Required | stringRequired | Dietary tag (e.g., "Vegan", "Keto", "Gluten-Free"), case-insensitive; CSV means all listed. A value that is not one of the 21 tags returns 400 invalid_parameter with the accepted tags in valid_diets, never an empty page. |
| page | integer | Page number (default: 1) |
| limit | integer | Results per page (default: 20, max: 100) |
curl -X GET "https://api.foodashi.com/api/recipes/by-diet?diet=Vegan&limit=10" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/by-meal-type
Hobby+
1 credit
Browse by Meal Type
Filter recipes by meal type such as Breakfast, Lunch, Dinner, Snack, or Dessert.
| Parameter | Type | Description |
|---|---|---|
| type Required | stringRequired | Meal type (e.g., "Breakfast", "Dinner", "Dessert") |
| page | integer | Page number (default: 1) |
| limit | integer | Results per page (default: 20, max: 100) |
curl -X GET "https://api.foodashi.com/api/recipes/by-meal-type?type=Breakfast" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/by-time
Hobby+
1 credit
Browse by Time
Find quick recipes by a maximum total time in minutes (prep + cook + rest, waits included), fastest first. For hands-on time, add max_active_minutes (browse conventions).
| Parameter | Type | Description |
|---|---|---|
| max_time_minutes | integer | Maximum total time in minutes (default: 30). Legacy alias: maxMinutes. A value that is not a positive whole number returns 400. |
| page | integer | Page number (default: 1) |
| limit | integer | Results per page (default: 20, max: 100) |
curl -X GET "https://api.foodashi.com/api/recipes/by-time?max_time_minutes=30" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/by-difficulty
Hobby+
1 credit
Browse by Difficulty
Filter recipes by difficulty: Easy, Hard or Expert. Medium is not accepted as a filter value and is refused with 400 invalid_parameter (what these fields mean).
| Parameter | Type | Description |
|---|---|---|
| difficulty Required | stringRequired | Difficulty: "Easy", "Hard" or "Expert", case-insensitive |
| page | integer | Page number (default: 1) |
| limit | integer | Results per page (default: 20, max: 100) |
curl -X GET "https://api.foodashi.com/api/recipes/by-difficulty?difficulty=Easy" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/count
Hobby+
1 credit
Recipe Count
Get the total number of published recipes. Supports optional filters to count subsets.
| Parameter | Type | Description |
|---|---|---|
| cuisine | string | Filter by cuisine |
| diet | string | Filter by dietary tag |
| difficulty | string | Filter by difficulty |
curl -X GET "https://api.foodashi.com/api/recipes/count?cuisine=Italian" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/cuisines
Hobby+
1 credit
List Cuisines
Get all cuisines that have published recipes, with recipe counts for each.
curl -X GET "https://api.foodashi.com/api/recipes/cuisines" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/diets
Hobby+
1 credit
List Dietary Tags
Get the dietary tags recipes are served with, with their counts. Useful for dietary filter UIs. A withheld diet (Low-FODMAP) is served on no recipe, so it is not listed; withheld_diets names it with the reason.
curl -X GET "https://api.foodashi.com/api/recipes/diets" \ -H "X-Api-Key: YOUR_API_KEY"
/api/autocomplete
Indie+
1 credit
Autocomplete
Extremely fast search for UI search bars. Returns lightweight titles matching the query: the text is matched anywhere in the title, case-insensitive, not only at its start (chi finds "Gnocchi di Patate" and "American Beef Chili").
| Parameter | Type | Description |
|---|---|---|
| q | stringRequired | Search text, 2 characters or more (alias query). A missing or one-character q returns 200 with an empty results list, not a 400. |
| limit | integer | Max results (default 5, max 10). A value below 1 or not a number is served as 5, a value above 10 as 10. |
curl -X GET "https://api.foodashi.com/api/autocomplete?q=chick" \ -H "X-Api-Key: YOUR_API_KEY"
{
"results": [
{ "id": "50ab7323-f52d-4765-9b0e-06cc2df8b6ce", "title": "Southern Fried Chicken" },
{ "id": "2d0660b3-f5f8-459f-b48e-5fdab657c14c", "title": "Chicken and Leek Pie" }
]
}
/api/bulk-lookup
Indie+
2 credits
Bulk Lookup
Fetch details for multiple recipes in a single API call. Essential for "Favorites" lists.
| Parameter | Type | Description |
|---|---|---|
| ids | stringRequired | Comma-separated recipe UUIDs (max 50). Ids that are not UUIDs come back in invalid_ids. |
| fields | CSV | Return only these top-level recipe fields (id, ai_generated and api_version always come). See Trimming the Recipe. |
curl -X GET "https://api.foodashi.com/api/bulk-lookup?ids=3f8a1c2e-9b4d-4e6f-8a2b-1c3d5e7f9a0b,9c1d2e3f-4a5b-4c6d-8e7f-0a1b2c3d4e5f" \ -H "X-Api-Key: YOUR_API_KEY"
/api/similar
Indie+
1 credit
Similar Recipes
Recipes from the same cuisine, those sharing a dish type with the source first, newest first. Perfect for "You might also like" features. Optional narrowing: diet, meal_type, dish_type and exclude_allergens, with the same rules as /api/recipes.
| Parameter | Type | Description |
|---|---|---|
| id | uuidRequired | Source recipe UUID |
| limit | integer | Max results (default 5, max 20). A value below 1 or not a number is served as 5, a value above 20 as 20. |
| fields | CSV | Return only these top-level recipe fields (id, ai_generated and api_version always come). See Trimming the Recipe. |
curl -X GET "https://api.foodashi.com/api/similar?id=3f8a1c2e-9b4d-4e6f-8a2b-1c3d5e7f9a0b" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/by-cooking-method
Indie+
1 credit
Browse by Cooking Method
Find recipes by cooking technique such as grilling, baking, stir-frying, or slow cooking.
| Parameter | Type | Description |
|---|---|---|
| method | stringRequired | Cooking method, case-insensitive (e.g., "grilling", "baking", "stir-frying"). Send the value recipes carry in cooking_methods (baked, grilled, fried) or the everyday word for it: baking or bake (baked), grilling (grilled), frying (fried), steaming, roasting, boiling, simmering, poaching, braising, searing, sauteing, stewed (stewing), broiled (broiling), barbecue or bbq (barbecuing), raw or no cook (no-cook). smoking (cold smoke) and smoked (hot smoke) are two different methods. Any other value returns 400 invalid_parameter with every accepted method in valid_values. |
| page | integer | Page number (default: 1) |
| limit | integer | Results per page (default: 20, max: 100) |
curl -X GET "https://api.foodashi.com/api/recipes/by-cooking-method?method=grilling" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/by-equipment
Indie+
1 credit
Browse by Equipment
Find recipes that use specific kitchen equipment like air fryer, slow cooker, or wok.
| Parameter | Type | Description |
|---|---|---|
| equipment | stringRequired | Equipment name (e.g., "oven", "air fryer", "slow cooker") |
| page | integer | Page number (default: 1) |
| limit | integer | Results per page (default: 20, max: 100) |
curl -X GET "https://api.foodashi.com/api/recipes/by-equipment?equipment=air+fryer" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/by-occasion
Indie+
1 credit
Browse by Occasion
Find the recipes served with an occasion: Weeknight, Family Meal, Party, Holiday or Meal Prep. See occasions.
| Parameter | Type | Description |
|---|---|---|
| occasion | stringRequired | One of "Weeknight", "Family Meal", "Party", "Holiday", "Meal Prep", case-insensitive. "Everyday" and any other value return 400 invalid_parameter with the five in valid_values. |
| page | integer | Page number (default: 1) |
| limit | integer | Results per page (default: 20, max: 100) |
curl -X GET "https://api.foodashi.com/api/recipes/by-occasion?occasion=Holiday" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/by-season
Indie+
1 credit
Browse by Season
Find seasonal recipes for a given time of year. Returns the recipes served with the requested season. A recipe with no known season (seasons: []) is not returned, and Year-Round is refused. See browse conventions for full details.
| Parameter | Type | Description |
|---|---|---|
| season | stringRequired | One of: Spring, Summer, Fall (or alias Autumn), Winter. Case-insensitive. Any other value returns 400 invalid_parameter with valid_values (Spring, Summer, Fall, Winter). |
| random | boolean | Shuffle results. See browse conventions. Aliases: sort=random, shuffle=true. |
| page | integer | Page number (default: 1). |
| limit | integer | Results per page (default: 20, max: 100). |
curl -X GET "https://api.foodashi.com/api/recipes/by-season?season=Spring&limit=20" \ -H "X-Api-Key: YOUR_API_KEY"
curl -X GET "https://api.foodashi.com/api/recipes/by-season?season=Spring&random=true&limit=10" \ -H "X-Api-Key: YOUR_API_KEY" # Each request returns a different 10: a fresh shuffle every time.
/api/search
Indie+
2 credits
Smart Search
Vector-powered semantic search. Find recipes by describing what you want in natural language, e.g. "spicy dinner under 500 cals", "comfort food for winter", "easy weeknight pasta".
Always returns results
If the embedding service is temporarily unavailable (rate limit, quota, network), this endpoint automatically falls back to a text match on title / title_english / description so your app keeps working. When this happens the response includes meta.mode = "text_fallback" with a note explaining the degradation. Vector-mode and text-fallback responses share the same shape.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| q Required | stringRequired | Natural language search query (e.g. "spicy dinner under 500 cals") |
| limit | integer | Max results, 1-50 (default 20). |
| cuisine | string | Cuisine name, case-insensitive |
| diet | string | Dietary tag, case-insensitive; CSV means all listed; unknown value returns 400 with valid_diets |
| meal_type | string | Meal type (e.g. Dinner) |
| dish_type | string | Dish type (e.g. Soup) |
| exclude_allergens | string | CSV of EU-14 allergens to exclude, case-insensitive (aliases allergen, exclude_alg); an unknown name returns 400 invalid_allergen |
| fields | CSV | Return only these top-level recipe fields, and similarity if you name it (id, ai_generated and api_version always come). See Trimming the Recipe. |
curl -X GET "https://api.foodashi.com/api/search?q=comfort+food+for+winter&limit=5" \ -H "X-Api-Key: YOUR_API_KEY"
Response
Both modes return the same shape: each item of data is the full recipe object (Response Schema) plus similarity, from 0 to 1 rounded to four decimals, or null on text_fallback.
{
"success": true,
"query": "comfort food for winter",
"count": 5,
"filters_applied": false,
"data": [
{
"id": "3f8a1c2e-9b4d-4e6f-8a2b-1c3d5e7f9a0b",
"title": "Pot-au-feu",
"title_english": "Beef and Vegetable Stew",
"description": "...",
"image_url": "https://...",
"total_time_minutes": 210,
"active_time_minutes": 40,
"servings": 6,
"cuisine": "French",
"cuisine_code": "FR",
"meal_types": ["Dinner", "Main Course"],
"dish_types": ["Stew"],
"difficulty": null,
"dietary_tags": ["Dairy-Free"],
"allergens": ["Celery"],
"nutrition": { "calories": 410, "protein_g": 38.5, ... },
"ingredients": [ ... ],
"steps": [ ... ],
...
"ai_generated": true,
"api_version": "v1",
"similarity": 0.8123
}
],
"meta": { "mode": "semantic", "semantic_ranking": true, "tier": "indie" }
}
meta.mode is "semantic" or "text_fallback". If a recipe could not be formatted, the body also carries "partial": true and failed_ids; if the data behind the answer could not be read, the answer is a retryable 503 service_unavailable. The search card’s meal_type and published_at are not part of the recipe: read meal_types[].
/api/recipes/by-nutrients
Pro+
3 credits
Browse by Nutrients
Filter recipes by nutrient ranges. Find high-protein, low-calorie, or custom macro recipes. Every bound is per serving, at least one is required, and each has a snake_case name too (min_calories, max_protein_g, max_saturated_fat_g, max_sugar_g, max_sodium_mg, as on /api/recipes/filter). The recipes come back whole, nutrition included.
| Parameter | Type | Description |
|---|---|---|
| minCalories | number | Minimum calories per serving (alias min_calories) |
| maxCalories | number | Maximum calories per serving (alias max_calories) |
| minProtein | number | Minimum protein (g) per serving (alias min_protein_g) |
| maxProtein | number | Maximum protein (g) per serving (alias max_protein_g) |
| minCarbs | number | Minimum carbs (g) per serving (alias min_carbs_g) |
| maxCarbs | number | Maximum carbs (g) per serving (alias max_carbs_g) |
| minFat | number | Minimum fat (g) per serving (alias min_fat_g) |
| maxFat | number | Maximum fat (g) per serving (alias max_fat_g) |
| minSaturatedFat | number | Minimum saturated fat (g) per serving (alias min_saturated_fat_g) |
| maxSaturatedFat | number | Maximum saturated fat (g) per serving (alias max_saturated_fat_g) |
| minFiber | number | Minimum fiber (g) per serving (alias min_fiber_g) |
| maxFiber | number | Maximum fiber (g) per serving (alias max_fiber_g) |
| minSugars | number | Minimum total sugars (g) per serving (alias min_sugar_g) |
| maxSugars | number | Maximum total sugars (g) per serving (alias max_sugar_g) |
| minSodium | number | Minimum sodium (mg) per serving (alias min_sodium_mg) |
| maxSodium | number | Maximum sodium (mg) per serving (alias max_sodium_mg) |
| page | integer | Page number (default: 1) |
| limit | integer | Results per page (default: 20, max: 100) |
curl -X GET "https://api.foodashi.com/api/recipes/by-nutrients?minProtein=25&maxCalories=500&maxSaturatedFat=5" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/by-ingredients
Indie+
2 credits
Browse by Ingredients
Find recipes that contain (or avoid) specific ingredients. Just send ingredient names. No need to look up UUIDs first. Powers "what’s in my fridge", allergen-avoidance, and pantry-based recipe discovery.
When to use this vs /api/ingredients/:id/recipes
Use this endpoint when you have an ingredient name (typical case: user types into a search box). Use /api/ingredients/:id/recipes only when you already have a specific canonical UUID from the autocomplete endpoint and want recipes that link to that exact entry.
| Parameter | Type | Description |
|---|---|---|
| include | stringRequired* | Comma-separated ingredient names to include. Each term is matched independently. Multi-term acts as AND by default: recipes must contain all listed terms (see match). Examples: chicken, chicken,rice, red onion,garlic. |
| ingredients | string | Another name for include. Send one or the other: both is a 400 invalid_parameter. |
| match | string | all (default): a recipe must hold every include term. any: a recipe holding any of them, for “cook with what you have”. Any other value is a 400 invalid_parameter. Echoed in filter.match. |
| exclude | string | Comma-separated ingredient names to exclude. Every published recipe that holds any listed term is left out, however many there are, matched by the same rules as include (a contained word, so exclude=egg also leaves out eggplant). Example: nuts,dairy. |
| random | boolean | Shuffle results. See browse conventions. |
| page | integer | Page number (default: 1). |
| limit | integer | Results per page (default: 20, max: 100). |
* At least one of include (or ingredients) or exclude is required. The shared filters of the browse conventions apply too, max_active_minutes and fields included.
Matching rules: no surprises
- • Case-insensitive:
Red Onion,red onion,RED ONIONall match identically. - • Whitespace-normalized:
red onion(extra spaces) matches the same asred onion. - • Plural-tolerant:
onion=onions,tomato=tomatoes,cherry=cherries. No need to guess what form the catalogue uses. - • Substring match:
chickenmatches "chicken breast", "ground chicken", "chicken thigh", etc. To be more specific, use a longer phrase likechicken breast. - • Typo-intolerant:
red ononreturns 0. For fuzzy/typo matching, hit/api/ingredients/autocompletefirst and pass the corrected name. - • Resolver-aware: even when the catalogue stores a recipe’s ingredient as generic ("Onions, raw") rather than the specific variant ("Red Onions"), this endpoint still finds recipes that mention
red onionin their original ingredient text. You get the result you expect without having to know about internal canonical IDs.
curl -X GET "https://api.foodashi.com/api/recipes/by-ingredients?include=red%20onion&limit=10" \ -H "X-Api-Key: YOUR_API_KEY"
curl -X GET "https://api.foodashi.com/api/recipes/by-ingredients?include=chicken,rice&limit=20" \ -H "X-Api-Key: YOUR_API_KEY"
curl -X GET "https://api.foodashi.com/api/recipes/by-ingredients?include=chicken,rice&exclude=nuts,dairy" \ -H "X-Api-Key: YOUR_API_KEY"
curl -X GET "https://api.foodashi.com/api/recipes/by-ingredients?ingredients=leek,potato,bacon&match=any&max_active_minutes=30" \ -H "X-Api-Key: YOUR_API_KEY"
{
"recipes": [
{
"id": "03905874-4035-4754-9418-13c687d7f429",
"title": "আলু ভর্তা",
"title_english": "Mashed Potato Bhorta",
"image_url": "https://.../recipe-...-1776634763681.png",
"total_time_minutes": 22,
"servings": 4,
"cuisine": "Bangladeshi",
"cuisine_code": "BD",
"difficulty": "Easy",
"dietary_tags": ["Vegan", "Gluten-Free", "Nut-Free", "Soy-Free"],
"allergens": [],
"nutrition": { "calories": 190, ... },
"ingredients": [ ... ],
"steps": [ ... ],
...
}
],
"filter": {
"include": ["red onion"],
"exclude": [],
"match": "all"
},
"pagination": {
"page": 1,
"limit": 10,
"total": 10,
"total_pages": 1
}
}
Error responses
| Status | Body | When |
|---|---|---|
400 | {"error":"At least one of include (or ingredients) or exclude parameter required"} | Neither include (nor ingredients) nor exclude provided. |
400 | {"code":"invalid_parameter",...} | Both include and ingredients sent, or a match other than all or any. |
400 | {"error":"Maximum 10 ingredients per include/exclude list"} | Too many comma-separated terms. |
200 | {"recipes":[],"pagination":{"total":0,...}} | No matches. Empty result, not an error. |
503 | {"error":"...","code":"service_unavailable","retryable":true} | An ingredient term could not be read. Retry; a page that ignored the term is never served. |
/api/recipes/:id/nutrition
Indie+
2 credits
Recipe Nutrition
Get a recipe's nutrition with the ingredient lines it is computed from: the eight nutrients per serving with their daily value percentages and caloric breakdown, and the same eight per 100 g.
| Parameter | Type | Description |
|---|---|---|
| :id | uuidRequired | Recipe UUID |
| servings | number | Optional, 0.5 to 100: the per-serving values for that many servings instead of the recipe's own. computed_for_servings echoes the count used. |
curl -X GET "https://api.foodashi.com/api/recipes/{id}/nutrition" \ -H "X-Api-Key: YOUR_API_KEY"
nutrition_per_serving carries calories, protein_g, fat_g, saturated_fat_g, carbohydrate_g, fiber_g, sugars_total_g and sodium_mg, plus net_carbs_g, daily_value_percent and caloric_breakdown. nutrition_per_serving.per_100g has the same eight keys per 100 g of the dish, as on the full recipe, each a number or null when the data does not state it (never 0); a ?servings= override never changes it.
/api/recipes/:id/allergens
Indie+
1 credit
Recipe Allergens
Allergen declaration covering all 14 EU-regulated allergens, hidden allergen sources, and cross-contamination warnings.
curl -X GET "https://api.foodashi.com/api/recipes/{id}/allergens" \ -H "X-Api-Key: YOUR_API_KEY"
allergen_declaration has the same shape as on the full recipe: framework, note and declaration, an object with contains, may_contain (each with its reason) and hidden_sources, or null. Here hidden_sources leaves out ingredients allergen_sources already names.
/api/recipes/:id/beverages
Indie+
1 credit
Beverage Pairing
AI-generated beverage pairing suggestions including wine, beer, cocktails, and non-alcoholic options.
curl -X GET "https://api.foodashi.com/api/recipes/{id}/beverages" \ -H "X-Api-Key: YOUR_API_KEY"
beverage_pairing carries six lists of strings, each always present (empty when there is nothing of that kind): wine, beer, cocktails, non_alcoholic, spirits and recommendations. A recipe without a pairing returns null.
beverage_pairing is a paid field (Indie plan and above). If it is ever served locked, it is the locked marker and note reads “Beverage pairings are included from the Indie plan.” In the Test Kitchen the body also carries "field_plans": {"beverage_pairing": "indie"}.
/api/recipes/:id/scale
Indie+
1 credit
Scale Servings
Recalculate all ingredient quantities for a different number of servings.
| Parameter | Type | Description |
|---|---|---|
| :id | uuidRequired | Recipe UUID |
| servings | integerRequired | Target number of servings (1-100) |
curl -X GET "https://api.foodashi.com/api/recipes/{id}/scale?servings=8" \ -H "X-Api-Key: YOUR_API_KEY"
Each line carries original_quantity and scaled_quantity (numbers, or null when the line has no usable amount), the edible grams the nutrition scales with (original_gram_weight, scaled_gram_weight), scaled_purchase_gram_weight, the scaled mass to buy (bone-in, shell-on, undrained; equal to scaled_gram_weight when nothing is removed before eating), and text, the scaled line as a cook reads it.
/api/recipes/:id/taste
Pro+
3 credits
Taste Profile
6-axis taste profile rated 1-10: sweetness, saltiness, sourness, bitterness, savoriness, and spiciness.
curl -X GET "https://api.foodashi.com/api/recipes/{id}/taste" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/:id/sustainability
Pro+
3 credits
Sustainability
The recipe's climate footprint: kg CO2 equivalent per serving and per recipe, estimated from each ingredient's weight and a climate factor for its food class. Also the plant-based share of the recipe's weight, the ingredients that contribute most, and how much of the recipe the estimate covers. No AI is involved: the same recipe always gives the same number.
- Scope: ingredients from farm to consumer, packaging and transport included; home cooking energy excluded. An estimate for comparing dishes, not a certified label.
coverage: the share of the recipe's weight (water excluded) that has a climate factor. An ingredient we cannot place is listed inuncovered_ingredientsand never guessed.food_class: the food class each top contributor was costed as, a readable label such asbeef,cheese,chocolateormilk or white chocolate. Milk and white chocolate count as animal-based inplant_based_share.- Source: climate factors from ADEME AGRIBALYSE v3.2 (see Data Sources).
curl -X GET "https://api.foodashi.com/api/recipes/{id}/sustainability" \ -H "X-Api-Key: YOUR_API_KEY"
{
"id": "…", "title": "Bánh tôm", "title_english": "Vietnamese Fried Shrimp and Pork Cakes",
"sustainability": {
"co2e_kg_per_serving": 0.49, "co2e_kg_total": 1.96, "servings": 4,
"plant_based_share": 0.59, "coverage": 1,
"top_contributors": [
{ "ingredient": "shrimp", "food_class": "shrimp, crab or lobster", "co2e_kg": 1.33, "share": 0.68 }
],
"uncovered_ingredients": [],
"scope": "Ingredients from farm to consumer, packaging and transport included; home cooking energy excluded.",
"method": "Estimate: each ingredient's weight times a climate factor for its food class …"
}
}
/api/recipes/:id/medical
Pro+
3 credits
Medical Cautions
Medical caution flags for conditions like diabetes, hypertension, gout, and kidney disease. Its dietary_tags are the tags the recipe is served with everywhere, and never include a withheld claim: Low-FODMAP is withheld on every recipe.
curl -X GET "https://api.foodashi.com/api/recipes/{id}/medical" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/compare
Pro+
4 credits
Compare Recipes
Side-by-side nutritional comparison of 2-5 recipes.
| Parameter | Type | Description |
|---|---|---|
| ids | stringRequired | Comma-separated recipe UUIDs (2-5 IDs) |
curl -X GET "https://api.foodashi.com/api/recipes/compare?ids=uuid1,uuid2" \ -H "X-Api-Key: YOUR_API_KEY"
The comparison array is not guaranteed to follow the order of ids: match each entry to your request by its id.
/api/v1/recipes/:id/reports
Every plan
0 credits
Report a Recipe
Let the people using your app tell us when a recipe looks wrong. Reporting is free on every plan, Hobby included: it costs 0 credits and still works when your key is over its monthly quota. Reports of a wrong allergen or a wrong diet tag are safety reports and alert the Foodashi team straight away, so the correction reaches every app that shows the recipe.
The unversioned path /api/recipes/:id/reports is the same endpoint. Send reports from your server like every other call: your API key never belongs in client-side code.
Request body (JSON)
| Field | Type | Description |
|---|---|---|
| :id Required | uuid | The recipe UUID, in the path. It must be a published recipe, or the answer is 404 recipe_not_found. |
| reasons Required | string[] | One or more of the reasons below (multi-select). A repeated reason counts once. |
| comment | string | Optional, at most 500 characters. Links are removed. Required when reasons includes other (a comment that is only a link counts as empty). |
| end_user_ref | string | Optional. Your own opaque id for the end user who reported, 1 to 64 characters of A-Z a-z 0-9 _ -. Never an email address. |
No other field is accepted: an unknown field returns 400 invalid_report naming it.
Reasons
| Reason | Use it when |
|---|---|
| allergen_wrong | An allergen is missing or listed wrongly. Safety report. |
| diet_tag_wrong | A diet tag such as Vegan or Gluten-Free does not fit the recipe. Safety report. |
| ingredient_wrong | An ingredient or a quantity is wrong or missing. |
| instructions_wrong | A step is wrong, missing or unclear. |
| time_wrong | The prep, cook or total time is wrong. |
| nutrition_wrong | A nutrition value looks wrong. |
| title_or_image_wrong | The title or the photo does not match the dish. |
| not_a_real_dish | The dish does not exist, or is not what it claims to be. |
| inappropriate | Offensive or unsuitable content. |
| other | Anything else. A comment is required. |
Limits and duplicates
- • Each API key can file 20 reports per hour and 200 per 24 hours. Over either limit the answer is
429 report_limit_exceededwith aRetry-Afterheader. - • The same key reporting the same recipe again for at least one of the same reasons within 24 hours gets the existing report back (
200,"duplicate": true). Nothing new is stored and it does not count toward the limits, so a double tap in your app is harmless. - • Reports never use your monthly credits, and they keep working when your key is over its monthly quota.
curl -X POST "https://api.foodashi.com/api/v1/recipes/b1a2c3d4-e5f6-7890-abcd-ef1234567890/reports" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"reasons": ["allergen_wrong"], "comment": "The sauce uses fish sauce but Fish is not listed.", "end_user_ref": "usr_7f3a92"}'
Responses
| Status | Code | Meaning |
|---|---|---|
| 202 | Report received and stored: {"report_id", "status": "received", "duplicate": false}. | |
| 200 | A duplicate of a report this key filed in the last 24 hours: the existing report_id with "duplicate": true. | |
| 400 | invalid_report | The body is not valid. field names the field at fault and message says why; a reason problem also returns accepted_reasons. |
| 401 | missing_api_key, invalid_api_key | No API key, or an unknown one. |
| 403 | customer_key_required | The request used a server credential rather than a customer API key. Reports are filed and limited per customer key. |
| 404 | recipe_not_found | No published recipe has this id. |
| 429 | report_limit_exceeded | This key reached 20 reports in an hour or 200 in 24 hours. window is "hour" or "day", limit is the limit reached, and retry_after (also the Retry-After header) is the wait in seconds. |
| 500 | report_not_saved | The report could not be stored right now. Retry later. |
{
"report_id": "5d0c7e8a-1f2b-4c3d-9e4f-6a7b8c9d0e1f",
"status": "received",
"duplicate": false
}
{
"report_id": "5d0c7e8a-1f2b-4c3d-9e4f-6a7b8c9d0e1f",
"status": "received",
"duplicate": true
}
{
"error": "Invalid report",
"code": "invalid_report",
"field": "comment",
"message": "comment is required when reasons includes 'other' ..."
}
{
"error": "Report limit reached",
"code": "report_limit_exceeded",
"message": "This API key reached its limit of 20 reports per hour ...",
"window": "hour",
"limit": 20,
"retry_after": 1260
}
end_user_ref is your own opaque id for the person who reported, such as your internal user id, so you can match a follow-up to your user. It accepts only letters, digits, _ and -, so it can never hold an email address; never send a name, email or phone number in any form. Do not put personal data in comment either: ask your users to describe what is wrong with the recipe, not anything about themselves.
Ingredients API: which endpoint do I use?
Foodashi exposes several ingredient endpoints. Pick by use case:
| I want to… | Endpoint | Why |
|---|---|---|
| Find recipes that use a specific ingredient (typed by user) | /api/recipes/by-ingredients?include=<name> | Single call. Handles case, plural, whitespace, generic-vs-variant collapse. Default choice for search bars. |
| Power a typeahead/autocomplete UI | /api/ingredients/autocomplete?q=<prefix> | Fast prefix + substring search. Quality-ranked. Returns UUIDs for follow-up calls. |
| Get nutrition/details for a specific ingredient | /api/ingredients/:id | Full nutrition breakdown + portions. Requires UUID from autocomplete. |
| Get recipes that link to one exact canonical UUID | /api/ingredients/:id/recipes | Use only when you already have the UUID. For name lookup, see the top row. |
| Find ingredients in a food group (Produce, Dairy, …) | /api/ingredients/by-food-group?group=<name> | Browse by category. |
| Convert units (cups ↔ grams) for an ingredient | /api/ingredients/:id/convert | Uses ingredient-specific portion weights. |
3-minute quick start
curl "https://api.foodashi.com/api/recipes/by-ingredients?include=red%20onion&limit=10" \ -H "X-Api-Key: YOUR_API_KEY"
That’s it. One call, name → recipes. No UUID dance required.
/api/ingredients/search
Hobby+
1 credit
Search Ingredients
Search through 340,000+ ingredients from our curated ingredient database. Supports filtering by type (ingredients vs branded products).
| Parameter | Type | Description |
|---|---|---|
| q Required | stringRequired | Search query (e.g., "chicken", "tomato") |
| limit | integer | Results per page (default: 20, max: 50). A value above 50 is served as 50. |
| offset | integer | Pagination offset (default: 0) |
| type | string | One of all (default), raw, ingredients, unbranded, branded, cooked, processed (ingredients and unbranded mean raw; filter is an older alias). Any other value returns 400 invalid_parameter with the accepted values in valid_values. |
curl -X GET "https://api.foodashi.com/api/ingredients/search?q=chicken&limit=5" \ -H "X-Api-Key: YOUR_API_KEY"
/api/ingredients/autocomplete
Hobby+
1 credit
Ingredient Autocomplete
Fast autocomplete for ingredient search bars: names that start with the query come first, then names that contain it anywhere. Returns canonical name, UUID, food group, category, and type. Quality-ranked so the most authoritative match comes first.
Tip
You usually don’t need to call autocomplete + then a recipe lookup. /api/recipes/by-ingredients?include=<name> handles the name → recipe lookup in a single call with built-in case & plural tolerance. Use autocomplete only when you need to surface a typeahead UI to the user.
| Parameter | Type | Description |
|---|---|---|
| q | stringRequired | Search query (min 2 chars). A missing or one-character q returns 200 with {"results": []}, not a 400. |
| limit | integer | Max results (default: 10, max: 20). A value above 20 is served as 20. |
curl -X GET "https://api.foodashi.com/api/ingredients/autocomplete?q=red%20onion" \ -H "X-Api-Key: YOUR_API_KEY"
{
"results": [
{
"id": "d642a69d-3c98-4c55-83a5-f231cde9eebd",
"name": "Red Onion",
"food_group": "11",
"food_group_name": "Vegetables and Vegetable Products",
"category": "Produce",
"ingredient_type": "raw"
},
{
"id": "f67dbb93-479b-4ea1-8a50-d6dc2cc33771",
"name": "Red Onions",
"food_group": "11",
"food_group_name": "Vegetables and Vegetable Products",
"category": "Produce",
"ingredient_type": "raw"
}
],
"query": "red onion",
"count": 2
}
query and count come with every answer to a query of 2 characters or more; the empty answer to a shorter one carries results only.
/api/ingredients/food-groups
Hobby+
1 credit
List Food Groups
Get all food groups with ingredient counts. Useful for building category navigation.
curl -X GET "https://api.foodashi.com/api/ingredients/food-groups" \ -H "X-Api-Key: YOUR_API_KEY"
/api/ingredients/:id
Indie+
1 credit
Ingredient Details
Get detailed nutrition information for a specific ingredient including macros, vitamins, minerals, and available portion sizes.
| Parameter | Type | Description |
|---|---|---|
| :id | uuidRequired | Ingredient UUID from search results |
{
"id": "abc123-def456...",
"name": "Chicken breast, raw",
"food_group": "Poultry Products",
"nutrition_per_100g": {
"calories": 165,
"protein": 31,
"fat": 3.6,
"carbohydrates": 0
},
"portions": [
{ "unit_name": "breast", "gram_weight": 174 }
],
"source": "Blended: USDA SR Legacy + CIQUAL (France)",
"license": "Multiple (see sources)",
"license_url": null,
"attribution_required": true,
"sources": [
{ "name": "USDA SR Legacy", "license": "CC0-1.0 (U.S. public domain)", "license_url": "https://creativecommons.org/publicdomain/zero/1.0/", "attribution": "...", "source_url": "https://fdc.nal.usda.gov" },
{ "name": "CIQUAL (France)", "license": "Licence Ouverte / Open Licence (ANSES)", "license_url": "...", "attribution": "...", "source_url": "https://ciqual.anses.fr" }
]
}
| Provenance field | Type | Description |
|---|---|---|
| source | string | Where the served values came from, e.g. "USDA SR Legacy". When several databases contribute it reads "Blended: A + B". |
| license | string | The licence of those values, or "Multiple (see sources)" for a blend. |
| license_url | stringnull | The licence text; null for a blend (each source has its own). |
| attribution_required | boolean | true when at least one source asks for a credit when you show its data. |
| sources | array | One entry per source database: name, license, license_url, attribution (the credit line to show) and source_url. |
/api/ingredients/search results carry the same five fields. Open Food Facts product rows are served only by /api/barcode, with its ODbL notice. Rows from the Indian Nutrient Databank (INDB) are never served: every by-id ingredient endpoint answers 404 for them, as for an unknown id.
In nutrition_per_100g, a value the source does not state is null, never 0; a measured 0 stays 0.
/api/barcode/:barcode
Indie+
1 credit
Barcode Lookup
Look up a packaged product by its UPC/EAN barcode and get its name, brand, and per-100g nutrition (energy, protein, fat, carbs, sodium, fiber, saturated fat, sugars), normalized to Foodashi's nutrient model. Data sourced from Open Food Facts under the ODbL; community-sourced nutrition is flagged with confidence 0.7. Results are cached. Every response carries the credit, the licence and a link to the product page (see Data licensing below).
| Parameter | Type | Description |
|---|---|---|
| :barcode | stringRequired | GTIN-8, UPC-A (12 digits), EAN-13 or GTIN-14, with a valid check digit |
curl -X GET "https://api.foodashi.com/api/barcode/3017620422003" \ -H "X-Api-Key: YOUR_API_KEY"
{
"barcode": "3017620422003",
"product_name": "Nutella",
"brand": "Ferrero, Nutella, Yum yum",
"country": "france",
"nutrition": {
"energy_kcal": 539,
"protein": 6.3,
"total_fat": 30.9,
"carbohydrate": 57.5,
"sodium": 0.0428,
"fiber": null,
"saturated_fat": 10.6,
"sugars_total": 56.3
},
"nutrition_confidence": 0.7,
"nutrition_gaps": ["fiber"],
"matched_canonical_id": null,
"data_origin": "openfoodfacts_live",
"source": "openfoodfacts",
"attribution": "Data from Open Food Facts (ODbL)",
"attribution_url": "https://world.openfoodfacts.org",
"license": "ODbL-1.0",
"license_url": "https://opendatacommons.org/licenses/odbl/1-0/",
"product_url": "https://world.openfoodfacts.org/product/3017620422003",
"cached_at": "2026-06-25T...",
"image_url": "https://images.openfoodfacts.org/images/products/301/762/042/2003/front_en.879.400.jpg",
"image_small_url": "https://images.openfoodfacts.org/images/products/301/762/042/2003/front_en.879.200.jpg",
"image_license": "CC BY-SA 3.0",
"image_attribution": "Open Food Facts",
"image_source": "https://world.openfoodfacts.org/product/3017620422003"
}
Nutrition is per 100 g, with sodium in grams per 100 g. A nutrient the product data does not state is null and is named in nutrition_gaps, never 0; a stated 0 stays 0.
| Image field | Type | Description |
|---|---|---|
| image_url | stringnull | Open Food Facts product photo (front of pack, ~400px), served unmodified from images.openfoodfacts.org. |
| image_small_url | stringnull | 200px thumbnail variant of image_url. |
| image_license | stringnull | Always "CC BY-SA 3.0" when an image exists, otherwise null. |
| image_attribution | stringnull | Always "Open Food Facts" when an image exists, otherwise null. |
| image_source | stringnull | Open Food Facts product page URL to link back to (e.g. https://world.openfoodfacts.org/product/{barcode}). |
Image licensing. All five image fields are null together when the product has no photo. Product images are Open Food Facts photos under CC BY-SA 3.0 (a separate license from the ODbL nutrition data). If your app displays the image you must attribute "Open Food Facts", keep the CC BY-SA 3.0 credit, link back (the image_attribution, image_license, and image_source fields carry exactly what you need), and show the image unmodified.
| Licence field | Type | Description |
|---|---|---|
| source | string | Always "openfoodfacts". |
| attribution | string | The credit line: "Data from Open Food Facts (ODbL)". |
| attribution_url | string | Open Food Facts home page, to link the credit to. |
| license | string | Licence of the product data: "ODbL-1.0". |
| license_url | string | The licence text. |
| product_url | string | This product’s page on Open Food Facts. |
| data_origin | string | "openfoodfacts_export" (served from our local copy of the Open Food Facts export) or "openfoodfacts_live" (fetched from Open Food Facts on a local miss). |
Data licensing. Product data is from Open Food Facts under the Open Database License (ODbL). If your app shows or redistributes it, credit Open Food Facts and keep the licence; the attribution, license, license_url, attribution_url and product_url fields carry what you need. Local hits (data_origin "openfoodfacts_export") carry no brand or photo: brand, country, quantity and the five image fields are null.
Errors. A code that is not a GTIN-8, UPC-A (12 digits), EAN-13 or GTIN-14 with a valid check digit returns 400 invalid_barcode before any lookup is made. An unknown product returns 404. When a lookup needs the live Open Food Facts service and its rate limit is reached, the answer is 503 with code upstream_rate_limited and a Retry-After header in seconds; retry after it.
/api/ingredients/:id/convert
Indie+
1 credit
Unit Conversion
Convert between different units for a specific ingredient using real portion data.
| Parameter | Type | Description |
|---|---|---|
| :id | uuidRequired | Ingredient UUID |
| amount | numberRequired | Amount to convert |
| from_unit | stringRequired | Source unit (e.g., "cup", "tbsp") |
| to_unit | string | Target unit (e.g., "g", "oz"; default: grams) |
curl -X GET "https://api.foodashi.com/api/ingredients/{id}/convert?amount=2&from_unit=cup&to_unit=g" \ -H "X-Api-Key: YOUR_API_KEY"
Errors. A unit the API does not know (parsec) returns 400 with code unknown_unit. A known unit this food has no portion or density data for (a cup of raw chicken breast) returns 404 with code no_conversion_data, rather than a guess. Both name the unit and list the units this food does convert in available_units, with a hint.
/api/ingredients/:id/substitutes
Indie+
1 credit
Ingredient Substitutes
Find nutritionally similar substitutes for an ingredient based on food group and macro profile.
| Parameter | Type | Description |
|---|---|---|
| :id | uuidRequired | Ingredient UUID from search results |
curl -X GET "https://api.foodashi.com/api/ingredients/{id}/substitutes" \ -H "X-Api-Key: YOUR_API_KEY"
{
"ingredient": { "id": "e595c015-faf4-4662-9ec9-2f2fe5589a4b", "name": "Chicken, broiler or fryers, breast, skinless, boneless, meat only, raw", "food_group": "5" },
"substitutes": [
{
"id": "445de72a-4cf2-4526-82f5-2a5a0a4eaae5",
"name": "Turkey Meat",
"slug": "turkey-whole-meat-only-raw",
"food_nature": "sr_legacy_food",
"food_group": "5",
"nutrition_per_100g": { "calories": 115, "protein": 22.6, "carbohydrates": 0.14, "fat": 1.93, "fiber": 0, "sugar": 0.07, "sodium": 118 },
"similarity": 0.973,
"similarity_score": 12,
"nutrition_diff": { "calories": -5, "protein": 0.1, "fat": -0.7, "carbohydrates": 0.1 }
}
]
}
Up to 10 cooking ingredients (never branded products) from the same food group, most similar first. similarity runs from 0 to 1 (higher is closer); similarity_score is the distance behind it (lower is closer). nutrition_diff is the substitute minus the original, per 100 g. An ingredient with no energy value on record returns an empty substitutes list with a note.
/api/ingredients/:id/recipes
Indie+
1 credit
Recipes Using Ingredient (by UUID)
Find all published recipes that use the canonical ingredient identified by UUID. The UUID typically comes from /api/ingredients/autocomplete or /api/ingredients/search.
For a name-based lookup, prefer /api/recipes/by-ingredients
This endpoint matches by exact canonical UUID. If you have an ingredient name (e.g. "red onion"), call /api/recipes/by-ingredients?include=red%20onion instead. It handles case, plural, whitespace, and resolves variants automatically.
| Parameter | Type | Description |
|---|---|---|
| :id | uuidRequired | Ingredient canonical UUID (from autocomplete or search). |
| random | boolean | Shuffle the matched recipes. See browse conventions. |
| page | integer | Page number (default: 1). |
| limit | integer | Results per page (default: 20, max: 50). |
| fields | CSV | Return only these top-level recipe fields (id, ai_generated and api_version always come). See Trimming the Recipe. |
Smart text fallback (2026-05-10)
If 0 recipes link to the exact canonical UUID, this endpoint automatically falls back to a text search using the canonical’s display name. So you get consistent results even when the ingredient resolver collapsed a variant (e.g. recipes mentioning "red onion" linking to generic "Onions, raw"). Hidden from the API consumer: you just see the right recipes.
curl -X GET "https://api.foodashi.com/api/ingredients/d642a69d-3c98-4c55-83a5-f231cde9eebd/recipes?limit=10" \ -H "X-Api-Key: YOUR_API_KEY"
{
"ingredient_id": "d642a69d-3c98-4c55-83a5-f231cde9eebd",
"recipes": [
{
"id": "eb0b3a12-8394-45ac-aa4c-d42f13e09ce5",
"title": "වම්බටු මෝජු",
"title_english": "Sri Lankan Eggplant Moju",
"image_url": "https://.../recipe-...-1776635175073.png",
"total_time_minutes": 66,
"servings": 6,
"cuisine": "Sri Lankan",
"difficulty": null,
"dietary_tags": ["Vegan", "Gluten-Free"],
"allergens": ["Mustard"],
"nutrition": { "calories": 215, ... },
"ingredients": [ ... ],
"steps": [ ... ],
...
}
],
"pagination": { "page": 1, "limit": 10, "total": 10, "total_pages": 1 },
"match_meta": { "fallback_attempted": false, "fallback_used": false }
}
Behavior notes
- • If 0 direct matches exist on the canonical link, the endpoint falls back to a text search using the canonical’s display name. You don’t need to know whether the recipe linked to the generic or specific variant. The result set is consistent.
- • Returns
400if:idis not a valid UUID. - • Returns
200with emptyrecipesarray when no matches (not a 404). - • Each item of
recipesis the full recipe object; a recipe that could not be formatted is named infailed_idswith"partial": true, and a failed read is a retryable503service_unavailable.
/api/ingredients/:id/allergens
Indie+
1 credit
Ingredient Allergen Check
Check which EU-14 allergens an ingredient contains or may contain.
curl -X GET "https://api.foodashi.com/api/ingredients/{id}/allergens" \ -H "X-Api-Key: YOUR_API_KEY"
/api/ingredients/by-food-group
Indie+
1 credit
Browse by Food Group
Browse ingredients within a food group. Accepts either a friendly category name ("Produce", "Dairy", "Bakery", "Pantry") or a USDA numeric food-group code ("11" for Vegetables). Case-insensitive for names.
| Parameter | Type | Description |
|---|---|---|
| group | stringRequired | Category name (e.g. Produce, Dairy) OR USDA numeric code (e.g. 11). Discover available groups via /api/ingredients/food-groups. |
| random | boolean | Shuffle. See browse conventions. |
| page | integer | Page number (default: 1). |
| limit | integer | Results per page (default: 20, max: 100). |
curl -X GET "https://api.foodashi.com/api/ingredients/by-food-group?group=Produce&limit=20" \ -H "X-Api-Key: YOUR_API_KEY"
curl -X GET "https://api.foodashi.com/api/ingredients/by-food-group?group=11&limit=20" \ -H "X-Api-Key: YOUR_API_KEY"
/api/ingredients/:id/portions
Indie+
1 credit
Ingredient Portions
Get all available portion sizes for an ingredient with gram weight equivalents.
curl -X GET "https://api.foodashi.com/api/ingredients/{id}/portions" \ -H "X-Api-Key: YOUR_API_KEY"
/api/ingredient-substitute
Hobby+
1 credit
Quick Ingredient Substitute
Lightweight ingredient-name substitute lookup (no UUID needed). Pass an ingredient name as a query parameter; receive common substitutes from a curated lookup table. For nutritionally-accurate substitutes use /api/ingredients/:id/substitutes instead.
| Parameter | Type | Description |
|---|---|---|
| ingredient or name or q Required | string | Ingredient name (e.g. "butter", "egg", "buttermilk"). Case-insensitive; partial matches accepted. All three parameter names are accepted. Use whichever fits your code style. |
curl -X GET "https://api.foodashi.com/api/ingredient-substitute?ingredient=butter" -H "X-Api-Key: KEY" curl -X GET "https://api.foodashi.com/api/ingredient-substitute?name=butter" -H "X-Api-Key: KEY" curl -X GET "https://api.foodashi.com/api/ingredient-substitute?q=butter" -H "X-Api-Key: KEY"
/api/ingredients/semantic-search
Pro+
2 credits
Semantic Ingredient Search
Find ingredients by meaning, not just keywords. Powered by vector embeddings, it handles synonyms, regional spellings, and culinary equivalents. Searching "cilantro" finds "coriander leaf"; "shrimp" finds "prawn". Useful for cross-locale recipe matching.
| Parameter | Type | Description |
|---|---|---|
| q Required | string | Natural-language ingredient query (e.g. "green peppers used in mexican food"). |
| limit | integer | Max results (default 10, max 50). |
| min_similarity | float | Cosine similarity threshold 0.0 to 1.0 (default 0.5). |
curl -X GET "https://api.foodashi.com/api/ingredients/semantic-search?q=fish%20sauce%20alternative&limit=5" \ -H "X-Api-Key: YOUR_API_KEY"
/api/recipes/analyze
Pro+
5 credits
Analyze Existing Recipe (by ID)
Run deep analysis on a specific recipe in our database. It returns its full classification (meal_types, dish_types, occasions), nutrition breakdown, allergen detection, dietary tag confidence, and NutriMetric score. Use this to enrich recipe metadata in your app without re-running the full /api/analyze/text endpoint.
| Body Field | Type | Description |
|---|---|---|
| recipe_id Required | uuid | Recipe UUID from /api/recipes or /api/search. |
curl -X POST "https://api.foodashi.com/api/recipes/analyze" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"recipe_id": "a1b2c3d4-e5f6-..."}'
Response
{
"recipe_id": "3f8a1c2e-9b4d-4e6f-8a2b-1c3d5e7f9a0b",
"title": "Pot-au-feu",
"title_english": "Beef and Vegetable Stew",
"source": "stored_analysis",
"classification": {
"cuisine": "French",
"meal_types": ["Dinner", "Main Course"],
"dish_types": ["Stew"],
"occasions": ["Family Meal"],
"seasons": ["Winter"],
"cooking_methods": ["Simmering"],
"difficulty": null,
"protein_source": "Beef"
},
"nutrition": { /* same nutrition object as /api/recipes/details */ },
"allergens": ["Celery"],
"allergen_declaration": { /* EU 1169/2011 declaration */ },
"dietary_tags": ["Dairy-Free"],
"dietary_tag_confidence": { "Dairy-Free": 0.95 },
"nutrimetric": { /* score and grade */ }
}
dietary_tag_confidence maps each tag to a confidence from 0 to 1. To analyse an ad hoc ingredient list instead, send {"ingredients": [{"ingredient_id": "<canonical ingredient UUID>", "amount": 250, "unit": "g"}], "servings": 4} (resolve names to ids with /api/ingredients/search). Send one body or the other, not both.
The ingredients body weighs masses only: unit is g, kg, mg, lb or oz, or a spelling of one such as grams or ounces (case-insensitive), and amount is a number from 0. A volume or piece unit (cup, tbsp, piece) is refused with a 400 invalid_parameter that lists supported_units: send grams, or use /api/nutrition/calculate, which weighs volumes and pieces from the food's own data. In the breakdown, a nutrient a food does not state is null on its line and in every total it enters, never 0.
/api/ingredients/batch
Pro+
5 credits
Batch Ingredient Lookup
Look up multiple ingredients at once. Much faster than individual searches for recipes.
{
"ingredients": ["chicken", "rice", "tomato"]
}
curl -X POST "https://api.foodashi.com/api/ingredients/batch" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"ingredients": ["chicken", "rice", "tomato"]}'
Or pass {"ids": ["<canonical ingredient UUID>", ...]} (max 50) to look up by id; results come back in your order with match_type "id". Names are also limited to 50 per call. Each found result carries ingredient.nutrition_per_100g, the values per 100 g by nutrient key; a value the source does not state is null, never 0.
/api/ingredients/by-nutrient
Pro+
3 credits
Ingredients by Nutrient
Find ingredients high or low in a specific nutrient. Great for meal planning and dietary goals.
| Parameter | Type | Description |
|---|---|---|
| nutrient | stringRequired | Nutrient name (e.g., "protein", "iron", "vitamin_c") |
| min | number | Minimum amount per 100g |
| max | number | Maximum amount per 100g |
| limit | integer | Results per page (default: 20, max: 50). A value above 50 is served as 50. |
curl -X GET "https://api.foodashi.com/api/ingredients/by-nutrient?nutrient=protein&min=20" \ -H "X-Api-Key: YOUR_API_KEY"
/api/ingredients/compare
Pro+
2 credits
Compare Ingredients
Side-by-side nutritional comparison of 2-5 ingredients per 100g.
| Parameter | Type | Description |
|---|---|---|
| ids | stringRequired | Comma-separated ingredient UUIDs (2-5 IDs) |
curl -X GET "https://api.foodashi.com/api/ingredients/compare?ids=uuid1,uuid2" \ -H "X-Api-Key: YOUR_API_KEY"
/api/nutrition/nutrients
Hobby+
1 credit
Nutrient List
Get all tracked nutrients with their units and categories (macros, vitamins, minerals, etc.).
curl -X GET "https://api.foodashi.com/api/nutrition/nutrients" \ -H "X-Api-Key: YOUR_API_KEY"
/api/nutrition/daily-values
Hobby+
1 credit
Daily Reference Values
FDA Daily Values for 31 nutrients, based on a 2,000 calorie diet (21 CFR 101.9, the current Nutrition Facts rule). A nutrient with no Daily Value, such as total sugars or trans fat, returns value: null with a note. The 50 g sugar reference applies to added sugars only, so recipe responses report daily_value_percent.sugars_total_g as null.
curl -X GET "https://api.foodashi.com/api/nutrition/daily-values" \ -H "X-Api-Key: YOUR_API_KEY"
/api/nutrition/daily-needs
Indie+
3 credits
Personalized Daily Needs
Estimate a person's daily calorie and macro targets (BMR, TDEE, protein/fat/carbohydrate) using the Mifflin-St Jeor equation. Deterministic and free of AI. Unlike Daily Values (the fixed FDA labeling reference), this personalizes to the individual. All inputs are optional: supply all five core inputs (age, weight, height, sex, activity level) for a personalized result; if any are missing, sensible defaults are applied, the result is flagged estimated, and each defaulted field is named in assumptions. This is always an estimate from a population formula, not medical or dietary advice.
| Body Field | Type | Description |
|---|---|---|
| age | integer | Age in years (1 to 120). Optional. |
| weight_kg | number | Body weight in kilograms (2 to 500). Optional. Use weight_lb for pounds instead. |
| height_cm | number | Height in centimetres (50 to 260). Optional. Use height_in for inches instead. |
| sex | string | "male", "female", or "unspecified". Optional. |
| activity_level | string | "sedentary", "light", "moderate", "active", or "very_active". Optional. |
| goal | string | "maintain", "lose", or "gain" (default "maintain"). Optional. |
curl -X POST "https://api.foodashi.com/api/nutrition/daily-needs" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"age":30,"weight_kg":75,"height_cm":180,"sex":"male","activity_level":"moderate","goal":"maintain"}'
{
"precision": "personalized",
"estimated": false,
"bmr_kcal": 1730,
"tdee_kcal": 2682,
"target_kcal": 2682,
"goal": "maintain",
"macros": { "protein_g": 120, "fat_g": 89, "carbohydrate_g": 350 },
"targets": { "fiber_g": 38, "saturated_fat_max_g": 30, "added_sugars_max_g": 67, "sodium_max_mg": 2300 },
"targets_note": "Fiber is a daily minimum (14 g per 1,000 kcal). Saturated fat and added sugars are upper limits (10% of calories). Sodium is the fixed FDA upper reference of 2,300 mg/day and is NOT personalized. For the full vitamin and mineral reference table, use GET /api/nutrition/daily-values.",
"inputs_used": { "age": 30, "weight_kg": 75, "height_cm": 180, "sex": "male", "activity_level": "moderate", "activity_label": "Moderate exercise 3-5 days/week" },
"assumptions": [],
"method": "Mifflin-St Jeor BMR x activity factor. Protein 1.6 g/kg, fat 30% of calories, carbohydrate remainder.",
"disclaimer": "Estimate only, not medical or dietary advice. ..."
}
| Response field | Type | Description |
|---|---|---|
| precision | string | "personalized" when all five core inputs were supplied, otherwise "estimated". |
| estimated | boolean | true when one or more inputs were defaulted. The output is always an estimate regardless. |
| bmr_kcal | integer | Basal metabolic rate from the Mifflin-St Jeor equation. |
| tdee_kcal | integer | Total daily energy expenditure (BMR × activity factor). |
| target_kcal | integer | Calorie target after applying the goal adjustment. |
| macros | object | Daily protein, fat, and carbohydrate targets in grams. |
| targets.fiber_g | integer | Daily fiber minimum in grams (eat at least this; 14 g per 1,000 kcal). |
| targets.saturated_fat_max_g | integer | Saturated fat upper limit in grams (stay under; ~10% of calories). |
| targets.added_sugars_max_g | integer | Added sugars upper limit in grams (stay under; ~10% of calories). |
| targets.sodium_max_mg | integer | Sodium upper limit in milligrams. Fixed FDA upper reference (2,300 mg/day); not personalized. |
| targets_note | string | Plain-language explanation that fiber is a minimum, saturated fat and added sugars are limits, and sodium is the fixed FDA reference (not personalized). |
| inputs_used | object | The effective inputs (including any defaults) used for the calculation, plus a human-readable activity_label. |
| assumptions | array | One entry per defaulted field, naming the field and the value assumed. Empty when fully personalized. |
| disclaimer | string | Always present. This is an estimate, not medical or dietary advice. |
Inputs outside what the equation describes are refused with a 400, never answered with a negative or empty target: a combination whose BMR is not positive, or whose protein (1.6 g per kg) and fat (30% of calories) already exceed the calorie target. activity_level must be one of sedentary, light, moderate, active or very_active.
/api/nutrition/calculate
Indie+
5 credits
Calculate Nutrition
Send a structured ingredient list and get complete nutrition back. Every name is resolved against our canonical ingredient database, then run through the same nutrition engine that powers the recipe catalog, so you get per-serving, per-100g and whole-recipe figures plus a NutriMetric grade. It makes no AI calls, which means it is fast, repeatable, and costs 5 rather than 175.
Which endpoint do I want? Use /api/nutrition/calculate when you already know your ingredients and amounts. Use /api/analyze/text when you have unstructured recipe text that still needs parsing. It reads the method as well, so it is the better choice for prose.
| Body Field | Type | Description |
|---|---|---|
| ingredients | arrayRequired | 1 to 100 items. Each needs a name plus either grams, or amount + unit. Optional preparation (up to 200 characters), written as a recipe would: a stated state or grade picks the matching food, so {"name":"butter","grams":30,"preparation":"unsalted, softened"} is costed as unsalted butter, not salted. |
| servings | number | Defaults to 1. Divides the total into per-serving figures. |
| steps | array | Optional method text. When supplied, discarded cooking media are accounted for: a marinade you lift the food out of, a salt crust, confit fat, dredging flour. Without it those are counted as eaten. |
| title | string | Optional dish name. Helps NutriMetric pick the right category. |
curl -X POST "https://api.foodashi.com/api/nutrition/calculate" -H "X-Api-Key: YOUR_API_KEY" -H "Content-Type: application/json" -d '{"ingredients":[{"name":"chicken breast","grams":300},{"name":"basmati rice","grams":200},{"name":"olive oil","amount":1,"unit":"tbsp"}],"servings":2}'
Response
Returns per_serving, per_100g and total nutrition, a nutrimetric grade, coverage and confidence, and an ingredients breakdown showing exactly which canonical food each name matched, so you can see why a number came out the way it did, and which items we could not match.
If no name matches a known food, or no matched food has nutrient data, the answer is 422 with code unprocessable_entity. If a nutrient read fails, the answer is a 503 with code service_unavailable (retry), never a total that counts the food as zero.
/api/nutrition/label
Indie+
3 credits
Nutrition Label
Generate FDA or EU format nutrition label data from custom ingredient lists. Returns structured label data with %DV calculations.
| Body Field | Type | Description |
|---|---|---|
| nutrition_per_serving | objectRequired | Pre-computed per-serving nutrients. Keys: calories, total_fat_g, saturated_fat_g, trans_fat_g, cholesterol_mg, sodium_mg, total_carbohydrate_g, dietary_fiber_g, total_sugars_g, added_sugars_g, protein_g |
| serving_size | string | Human-readable serving size, e.g. "2/3 cup (55g)" |
| servings_per_container | integer | Servings per container/recipe |
| format | string | "fda" or "eu" (default: "fda") |
curl -X POST "https://api.foodashi.com/api/nutrition/label" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"nutrition_per_serving":{"calories":250,"total_fat_g":12,"saturated_fat_g":3,"cholesterol_mg":30,"sodium_mg":470,"total_carbohydrate_g":31,"dietary_fiber_g":2,"total_sugars_g":5,"added_sugars_g":2,"protein_g":18},"serving_size":"2/3 cup (55g)","servings_per_container":8,"format":"fda"}'
/api/nutrition/nutrimetric
Indie+
3 credits
NutriMetric Calculator
Calculate NutriMetric nutrition quality score (A to F grade, 0-100 scale) for custom nutrition data. Uses 4 scoring pillars: protein density, calorie density, fat balance, and sodium. Category-aware for beverages, soups, desserts, and more.
| Body Field | Type | Description |
|---|---|---|
| nutrition_per_100g | objectRequired | Nutrients per 100g. Required: energy_kcal, protein_g, fat_g, carbohydrate_g, sodium_mg (sodium in mg). Bonus (improve score): fiber_g, saturated_fat_g, sugars_total_g. Legacy aliases accepted (calories, protein, total_fat, carbohydrate/carbs, sodium). |
| category | string | "main", "beverage", "soup", "salad", "dessert", or "snack" (auto-detected from nutrient profile if omitted) |
curl -X POST "https://api.foodashi.com/api/nutrition/nutrimetric" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"nutrition_per_100g":{"energy_kcal":190,"protein_g":12,"fat_g":8,"carbohydrate_g":15,"sodium_mg":400,"fiber_g":2,"saturated_fat_g":3,"sugars_total_g":5},"category":"main"}'
/api/nutrition/log
Pro+
85 credits
Natural-Language Meal Logging
Send free text describing what someone ate and get a per-item breakdown of foods with estimated grams and an 8-nutrient profile each, plus a summed total. This is an AI endpoint (one Gemini parse followed by deterministic nutrition lookup) billed at 85 credits. It is stateless: it computes and returns, it stores nothing. Portions are inferred from the text and some nutrition values are AI-estimated (flagged at nutrition_confidence 0.7, versus 1.0 for USDA values). Free and Indie developers can try it in the Test Kitchen on the trial budget. Limits: 500-character max input, 20 items max per call.
| Body Field | Type | Description |
|---|---|---|
| text | stringRequired | Free text describing what was eaten (for example "2 scrambled eggs, a slice of whole wheat toast, and a cup of orange juice"). Max 500 characters, up to 20 items. |
curl -X POST "https://api.foodashi.com/api/nutrition/log" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"text":"2 scrambled eggs, a slice of whole wheat toast, and a cup of orange juice"}'
{
"input": "2 scrambled eggs, a slice of whole wheat toast, and a cup of orange juice",
"items": [
{
"food": "egg",
"quantity": 2,
"unit": "medium",
"grams": 100,
"matched_canonical_id": "ec721176-...",
"matched_name": "Egg, whole, raw, fresh",
"match_confidence": 1,
"nutrition": { "energy_kcal": 143, "protein": 12.6, "total_fat": 9.5, "carbohydrate": 0.7, "sodium": 142, "fiber": 0, "saturated_fat": 3.1, "sugars_total": 0.4 },
"nutrition_confidence": 1
},
{
"food": "whole wheat toast",
"quantity": 1,
"unit": "slice",
"grams": 30,
"matched_name": "Whole Wheat Toast",
"match_confidence": 1,
"nutrition": { "energy_kcal": 102, "protein": 1.8, "total_fat": 1.2, "carbohydrate": 19.7, "sodium": 132, "fiber": 2.8, "saturated_fat": 0.3, "sugars_total": 1.7 },
"nutrition_confidence": 1
},
{
"food": "orange juice",
"quantity": 1,
"unit": "cup",
"grams": 240,
"matched_name": "Orange juice, raw",
"match_confidence": 1,
"nutrition": { "energy_kcal": 113, "protein": 1.6, "total_fat": 0.5, "carbohydrate": 26.4, "sodium": 2.4, "fiber": 0.5, "saturated_fat": 0.1, "sugars_total": 20.8 },
"nutrition_confidence": 1
}
],
"total": { "energy_kcal": 358, "protein": 16, "total_fat": 11.2, "carbohydrate": 46.8, "sodium": 276.4, "fiber": 3.3, "saturated_fat": 3.5, "sugars_total": 22.9 },
"item_count": 3,
"unresolved": [],
"confidence": 1,
"disclaimer": "Portions inferred from text; nutrition is per-ingredient estimated or USDA. Stateless, nothing is stored.",
"source": "foodashi"
}
| Response field | Type | Description |
|---|---|---|
| items | array | One entry per parsed food, with the parsed quantity/unit, the estimated grams, the matched canonical (matched_canonical_id, matched_name) with match_confidence (0 to 1), the per-item nutrition for that gram amount, and nutrition_confidence (1.0 USDA, 0.7 AI-estimated). |
| nutrition | object | The 8 nutrients per item: energy_kcal, protein, total_fat, carbohydrate, sodium, fiber, saturated_fat, sugars_total. |
| total | object | The same 8 nutrients, summed across all items. |
| item_count | integer | Number of parsed items. |
| unresolved | array | Foods that could not be matched, returned honestly. |
| confidence | number | Aggregate confidence across the meal (0 to 1). |
Errors. If the meal text cannot be parsed, the answer is 502 (rephrase it). If a food lookup or a nutrient read fails after the parse, the answer is 503 service_unavailable with "retryable": true, never a meal with that food counted as zero or as unmatched, and nothing is cached. The AI parse has already run, so, as for any error after AI tokens were billed, the call’s credits stay spent.
/api/convert
Hobby+
1 credit
Unit Converter
General-purpose unit conversion with ingredient-specific density adjustments. Supports 30+ units including cups, tablespoons, ounces, and more.
| Parameter | Type | Description |
|---|---|---|
| amount | numberRequired | Amount to convert, above 0 and up to 100,000 |
| from | stringRequired | Source unit (e.g., "cup", "tbsp", "oz") |
| to | string | Target unit (default: "g") |
| ingredient | string | Ingredient for density-adjusted conversion (e.g., "flour", "sugar"). Required for a piece unit. |
curl -X GET "https://api.foodashi.com/api/convert?amount=1&from=cup&to=g&ingredient=flour" \ -H "X-Api-Key: YOUR_API_KEY"
{
"original": { "amount": 1, "unit": "cup" },
"converted": { "amount": 125, "unit": "g" },
"grams": 125,
"ingredient": "flour"
}
- • Every answer carries
grams, the weight of the original amount, also whentoisg. - • US volume units (
fl oz,pint,quart,gallon) follow the ingredient’s density through cups and tablespoons, so a pint of flour weighs what two cups of flour weigh. - • A piece unit (
piece,whole,small,medium,large) needs aningredientwhose piece weight is known, such asingredient=egg; without one it returns400. - • A unit the API does not recognise, on either side, returns
400with codeunknown_unit, ahintand thesupported_units. No weight is guessed. - • Large amounts are converted in full: there is no cap on the result.
/api/allergens
Hobby+
1 credit
Allergen List
Complete list of EU 14 regulated allergens plus extended allergens, with EU regulation metadata.
curl -X GET "https://api.foodashi.com/api/allergens" \ -H "X-Api-Key: YOUR_API_KEY"
/api/diets
Hobby+
1 credit
Diet List
All 21 dietary tags, grouped by category: lifestyle, low-carb, allergen-free, nutritional, regional and other. These are exactly the values diet= accepts. Each entry carries withheld: it is true for Low-FODMAP, which is accepted but served on no recipe, with the reason in withheld_diets.
curl -X GET "https://api.foodashi.com/api/diets" \ -H "X-Api-Key: YOUR_API_KEY"
/api/cuisines
Hobby+
1 credit
Cuisine List
All 81 world cuisines grouped by region: East/Southeast Asia, South & Central Asia, Middle East, North Africa, Europe, Nordic, North America, South America, Caribbean, Pacific, and Sub-Saharan Africa.
curl -X GET "https://api.foodashi.com/api/cuisines" \ -H "X-Api-Key: YOUR_API_KEY"
/api/analyze/text
Pro+
175 credits
Analyze Recipe Text
Extract a recipe from raw text using AI, then run the full nutrition pipeline: 8 nutrients with %DV, EU 14 allergens, dietary tags, NutriMetric scoring, and the recipe's climate footprint (sustainability, the same fields as Sustainability).
Request Body
| Field | Type | Description |
|---|---|---|
| text RequiredRequired | string | Raw recipe text (blog post, pasted instructions, typed). Min 20 characters. |
| servings | integer | Override detected servings count. |
Response Highlights
- recipe: Extracted title, servings, ingredients with amounts/units/preparation
- nutrition: per_serving, per_100g, per_recipe with 8 nutrients + %DV
- allergens / may_contain: EU 14 allergen detection with sources
- dietary_tags: from the 21 dietary tags (Vegan, Keto, Gluten-Free, etc.)
- nutrimetric: Proprietary nutrition grade (A to F, A best)
curl -X POST "https://api.foodashi.com/api/analyze/text" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"text": "Chicken Parmesan\n\n2 chicken breasts\n1 cup breadcrumbs\n1 cup marinara\n1 cup mozzarella\n\n1. Bread chicken. 2. Fry. 3. Top with sauce and cheese. 4. Bake 375F 20 min."}'
Errors. If a nutrient read fails after the recipe is extracted, the answer is 503 service_unavailable with "retryable": true, never totals that leave a food out or count it as zero. The AI extraction has already run, so the call’s credits stay spent.
/api/analyze/url
Pro+
275 credits
Analyze Recipe URL
Fetch a recipe from any URL, extract it with AI, then run the full nutrition + allergen + NutriMetric pipeline, with the recipe's climate footprint (sustainability). Works with most recipe blogs and cooking sites.
Request Body
| Field | Type | Description |
|---|---|---|
| url RequiredRequired | string | Full URL to a recipe page (http/https). |
| servings | integer | Override detected servings count. |
curl -X POST "https://api.foodashi.com/api/analyze/url" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://cooking.nytimes.com/recipes/1015819-chocolate-chip-cookies"}'
Errors. 502 when the page cannot be fetched, 422 when no recipe is found on it. If a nutrient read fails after the recipe is extracted, the answer is 503 service_unavailable with "retryable": true, never totals that leave a food out or count it as zero. The AI extraction has already run, so the call’s credits stay spent.
/api/recognize/image
Pro+
65 credits
Recognize Dish from Photo
Upload a food photo (base64 or URL) and get dish identification with confidence score, estimated ingredients with gram weights, estimated nutrition, and matching recipes from our database.
Request Body
| Field | Type | Description |
|---|---|---|
| image Required | string | Base64-encoded image (supports data URI). Max 4MB. Provide image OR image_url. |
| image_url | string | URL to fetch the image from (JPEG, PNG, WebP, GIF). |
| include_recipes | boolean | Include matching recipes from database (default: true). |
| max_results | integer | Max recipe matches to return (default: 5, max: 10). |
curl -X POST "https://api.foodashi.com/api/recognize/image" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"image_url": "https://example.com/pizza-photo.jpg", "max_results": 3}'
Matched recipes. Each item of data.matched_recipes is the full recipe object, under your plan. To trim it, add fields to the query string (/api/recognize/image?fields=title,image_url,nutrition); it is checked before the image is analysed, so a typo is a free 400 unknown_field (Trimming the Recipe).
Partial answers. If the recipe lookup fails after the dish is identified, the identification is still served with matched_recipes: [], and data carries "partial": true and errors: [{"code": "service_unavailable", "field": "matched_recipes", "message": "...", "retryable": true}]. Both fields appear only then: an empty matched_recipes without them means no recipe matched.
/api/recognize/ingredients
Pro+
110 credits
Recognize Ingredients from Photo
Snap a photo of your fridge, pantry, or counter and get every ingredient identified with estimated quantities, canonical DB matches (for precise nutrition lookup), storage suggestions, and recipe ideas.
Request Body
| Field | Type | Description |
|---|---|---|
| image Required | string | Base64-encoded image. Provide image OR image_url. |
| image_url | string | URL to fetch the image from (JPEG, PNG, WebP, GIF). |
| include_recipes | boolean | Include recipe suggestions using detected ingredients (default: true). |
| max_results | integer | Max recipe suggestions (default: 5, max: 10). |
curl -X POST "https://api.foodashi.com/api/recognize/ingredients" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"image_url": "https://example.com/fridge-contents.jpg"}'
Suggested recipes. Each item of data.suggested_recipes is the full recipe object, under your plan. To trim it, add fields to the query string (/api/recognize/ingredients?fields=title,ingredients); it is checked before the image is analysed, so a typo is a free 400 unknown_field (Trimming the Recipe).
/api/pairing/beverages
Pro+
90 credits
AI Sommelier: Beverage Pairing
Get expert beverage pairings from an AI Master Sommelier. Supports wine, beer, cocktails, spirits, and culturally-specific non-alcoholic beverages. Accepts dish names, ingredient lists, food photos, or recipe IDs.
Request Body (provide exactly one)
| Field | Type | Description |
|---|---|---|
| dish | string | Dish name (e.g., "Pad Thai", "Feijoada"). |
| ingredients | string[] | Ingredient list. AI identifies the likely dish first. |
| image | string | Base64-encoded food photo. Provide image OR image_url. |
| image_url | string | URL to a food photo (JPEG, PNG, WebP, GIF). |
| recipe_id | string | UUID of a published recipe in the database. |
Optional Preferences
| Field | Type | Description |
|---|---|---|
| preferences.no_alcohol | boolean | Only return non-alcoholic beverages. |
| preferences.budget | string | "low", "medium", or "high". Affects price range of suggestions. |
| preferences.style | string | "casual" or "fine-dining". Affects formality of recommendations. |
Response
Returns 5 beverage categories with specific names, reasoning, and serving details.
| Field | Description |
|---|---|
| pairings.wine[] | Specific varietals with region, price range, serving temp, and alternatives. |
| pairings.beer[] | Beer styles with category, price range, serving temp, and alternatives. |
| pairings.cocktails[] | Culturally-relevant cocktails with base spirit and brief recipe. |
| pairings.spirits[] | Neat pours or simple serves with serving suggestion. |
| pairings.non_alcoholic[] | Culturally-specific beverages (Lassi, Horchata, etc.) with brief recipe. |
| sommelier_note | Overarching pairing philosophy for the dish. |
| confidence | 0.0 to 1.0 confidence score for the pairing. |
curl -X POST "https://api.foodashi.com/api/pairing/beverages" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"dish": "Feijoada"}'
curl -X POST "https://api.foodashi.com/api/pairing/beverages" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"dish": "Biryani", "preferences": {"no_alcohol": true, "style": "fine-dining"}}'
/api/meal-plan/generate
Pro+
400 credits
Generate Meal Plan
AI-powered meal plan generator. Specify calorie targets, macro ratios, dietary restrictions, allergen exclusions, and cuisine preferences. Returns a day-by-day plan with real DB recipes (full nutrition) plus AI-generated suggestions where needed. Includes daily/weekly nutrition totals and a shopping-list-compatible format.
Request Body
| Field | Type | Description |
|---|---|---|
| days | integer | Plan duration, 1-14 (default: 7). |
| goal | string | maintain, lose_weight, gain_weight, or build_muscle. |
| calories_per_day | integer | Override calorie target (auto-set by goal if omitted). |
| dietary_restrictions | string[] | Array of dietary tags: ["Dairy-Free", "Gluten-Free"]. A withheld diet (Low-FODMAP) is refused with 400 invalid_parameter and a reason: no recipe is served with it. |
| allergen_exclusions | string[] | EU 14 allergens to exclude: ["Peanuts", "Tree Nuts"] |
| cuisine_preferences | string[] | Preferred cuisines: ["Japanese", "Italian"] |
| meals_per_day | string[] | Meal types: ["breakfast","lunch","dinner"] (also: snack, brunch). |
| servings | integer | Servings per meal (default: 2). |
curl -X POST "https://api.foodashi.com/api/meal-plan/generate" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "days": 7, "goal": "lose_weight", "calories_per_day": 1600, "dietary_restrictions": ["Dairy-Free"], "meals_per_day": ["breakfast", "lunch", "dinner"], "servings": 2 }'
/api/meal-plan/customize
Pro+
200 credits
Customize Meal Plan
Modify an existing meal plan: swap a specific meal with a chosen recipe, regenerate all meals for a day, or regenerate a single meal. Pass the plan object from the generate endpoint.
Request Body
| Field | Type | Description |
|---|---|---|
| planRequired | object | The plan object from /api/meal-plan/generate response. |
| actionRequired | string | swap, regenerate_day, or regenerate_meal. |
| day_indexRequired | integer | 0-based day index to modify. |
| meal_index | integer | 0-based meal index (required for swap and regenerate_meal). |
| swap_recipe_id | uuid | Recipe ID to swap in (required for swap action). |
| reason | string | Optional context for AI regeneration. |
curl -X POST "https://api.foodashi.com/api/meal-plan/customize" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"plan": {...}, "action": "swap", "day_index": 0, "meal_index": 2, "swap_recipe_id": "uuid"}'
/api/meal-plan/templates
Indie+
1 credit
Meal Plan Templates
Browse 8 pre-built meal plan templates covering common goals: Balanced Weight Loss, Muscle Building, Mediterranean Heart Health, Plant-Based Week, Keto, Family Friendly, Quick Meals, and Clean Bulk.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| goal | string | Filter by goal: lose_weight, maintain, gain_weight, build_muscle. Any other value returns 400 invalid_parameter with the goals in valid_values. |
curl -X GET "https://api.foodashi.com/api/meal-plan/templates?goal=lose_weight" \ -H "X-Api-Key: YOUR_API_KEY"
/api/shopping-list/from-recipes
Indie+
2 credits
Shopping List from Recipes
Generate a consolidated shopping list from multiple recipe IDs. Ingredients are merged, de-duplicated, and organized by USDA food groups (Produce, Protein, Dairy, Grains, Pantry, Beverages, Other).
curl -X POST "https://api.foodashi.com/api/shopping-list/from-recipes" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"recipe_ids": ["uuid-1", "uuid-2"], "unit_system": "metric"}'
/api/shopping-list/from-meal-plan
Pro+
2 credits
Shopping List from Meal Plan
Generate a shopping list directly from a meal plan (the shopping_list_compatible field from /api/meal-plan/generate). Automatically extracts all recipe IDs and applies per-meal servings overrides.
curl -X POST "https://api.foodashi.com/api/shopping-list/from-meal-plan" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"meal_plan": {"days": [{"meals": [{"recipe_id": "uuid", "servings": 2}]}]}}'
Each meal is bought for its servings, a number from 1 to 100, or for the recipe's own servings when you leave it out; repeated meals of one recipe add up, never capped. Any other servings value (0, negative, above 100, a string) is refused with a 400 invalid_parameter naming meal_plan.days[i].meals[j].servings.
/api/shopping-list/optimize
Pro+
150 credits
Optimize Shopping List
AI-powered shopping list optimization. Groups items by store aisle, suggests cheaper substitutions, estimates total cost, and provides storage tips.
curl -X POST "https://api.foodashi.com/api/shopping-list/optimize" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"recipe_ids": ["uuid-1", "uuid-2"], "unit_system": "metric", "preferences": {"budget": "normal", "diet": "none"}}'
A meal_plan body (instead of recipe_ids) follows the same servings rule as Shopping List from Meal Plan: 1 to 100 per meal, or omitted for the recipe's own servings.
/api/tips
Indie+
75 credits
Cooking & Prep Tips
AI-powered cooking and preparation tips for any ingredient or recipe name. Supports any language. Choose between cooking tips, prepping tips, or both. When a match is found in the database, tips are enriched with allergen, cuisine, and nutritional context for more specific advice.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Ingredient or recipe name (any language, 1-200 chars) |
type |
string | Yes | "ingredient" or "recipe" |
tip_type |
string | No | "cooking", "prepping", or "both" (default: "both") |
curl -X POST "https://api.foodashi.com/api/tips" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "chicken thigh", "type": "ingredient", "tip_type": "cooking"}'
{
"success": true,
"data": {
"name": "chicken thigh",
"name_english": "Chicken Thigh",
"type": "ingredient",
"tip_type": "cooking",
"tips": [
{
"category": "temperature",
"title": "Optimal Internal Temperature",
"tip": "Cook to 74°C (165°F) internal..."
}
],
"tip_count": 5,
"db_enriched": true
}
}
/stats
API Stats
Public totals for the catalogue, with no API key needed: published recipes, ingredients, nutrients, allergens, diets and cuisines.
It serves totals only, no breakdown by cuisine, diet or anything else. total_diets and total_cuisines are the lengths of the lists /api/diets and /api/cuisines serve. Rate limited per client address; the figures are cached for up to five minutes.
{
"total_recipes": 6596,
"total_ingredients": 1069820,
"total_nutrients": 64,
"total_allergens": 14,
"total_diets": 21,
"total_cuisines": 81,
"data_sources": 8,
"api_version": "v1",
"pipeline_version": "3.0-pipeline",
"status": "operational"
}
api_version is the API contract version. pipeline_version is a build label, not part of the contract. total_ingredients is an estimate over the full ingredient table.
Response Schema
Example response from /api/recipes/details, as a paid plan receives it. Every endpoint that returns recipes returns this same object (which ones, and how to trim it). nutrimetric and beverage_pairing are paid fields (Indie plan and above): see Paid Fields for what a Hobby key and the Test Kitchen receive.
{
"id": "a1b2c3d4-e5f6-...",
"title": "Caldo de Res",
"title_english": "Mexican Beef Soup",
"description": "A hearty Mexican beef soup...",
"image_url": "https://...",
"prep_time_minutes": 25,
"cook_time_minutes": 90,
"rest_time_minutes": 0,
"active_time_minutes": 115,
"total_time_minutes": 115,
"requires_advance_preparation": false,
"untimed_minutes": 17,
"overlap_minutes": 0,
"servings": 4,
"cuisine": "Mexican",
"cuisine_code": "MX",
"meal_types": ["Dinner", "Main Course"],
"dish_types": ["Stew"],
"difficulty": null,
"cost_level": 2,
"protein_source": ["Beef"],
"tags": ["authentic", "comfort-food"],
"cooking_methods": ["Braising", "Simmering"],
"occasions": [],
"seasons": ["Winter", "Fall"],
"equipment": ["Dutch Oven", "Knife"],
"dietary_tags": ["Gluten-Free"],
"allergens": ["Celery"],
"allergen_declaration": {
"framework": "EU Regulation 1169/2011 (14 allergens)",
"declaration": {
"contains": ["Celery"],
"may_contain": [],
"hidden_sources": [{ "allergen": "Celery", "hidden_in": ["beef stock"] }]
},
"note": "Derived from ingredient data to support your own labelling; not a legal declaration."
},
"nutrition": {
"calories": 455,
"protein_g": 35.2,
"fat_g": 18.1,
"saturated_fat_g": 5.2,
"carbohydrate_g": 43.0,
"fiber_g": 8.3,
"sugars_total_g": 6.1,
"sodium_mg": 850,
"net_carbs_g": 34.7,
"daily_value_percent": { "calories": 23, "protein_g": 70, "sodium_mg": 37, ... },
"caloric_breakdown": { "percent_protein": 30, "percent_fat": 34, "percent_carbs": 36 },
"per_100g": { "calories": 98, "protein_g": 7.6, "fat_g": 3.9, "saturated_fat_g": 1.1, "carbohydrate_g": 9.3, "fiber_g": 1.8, "sugars_total_g": 1.3, "sodium_mg": 183 }
},
"nutrimetric": {
"grade": "B",
"score": 68,
"label": "Good",
"version": "nutrimetric-1.0"
},
"taste_profile": {
"spicy": 3,
"salty": 5,
"sweet": 2,
"umami": 7,
"bitter": 1,
"sour": 2,
"scale": "1-10"
},
"storage": {
"fridge_days": 3,
"freezer_days": 90,
"instructions": "Cool, then refrigerate in an airtight container. Reheat on the stovetop until simmering..."
},
"beverage_pairing": { "wine": ["Tempranillo"], "beer": ["Negra Modelo"], "cocktails": ["Michelada"], "non_alcoholic": ["Agua de jamaica"], "spirits": [], "recommendations": ["A malty Negra Modelo matches the beef broth without overpowering the vegetables."] },
"ingredients": [
{
"text": "450 g Beef chuck",
"name": "Beef chuck",
"quantity": 450,
"unit": "g",
"is_main": true,
"preparation": "cubed",
"optional": false,
"gram_weight": 450,
"purchase_gram_weight": 450
},
{
"text": "900 g beef shank, bone-in",
"name": "beef shank, bone-in",
"quantity": 900,
"unit": "g",
"is_main": true,
"preparation": null,
"optional": false,
"gram_weight": 630,
"purchase_gram_weight": 900
}
],
"steps": [
{
"step": 1,
"text": "Sear the beef...",
"duration_minutes": 8,
"temperature": null,
"heat_level": "medium-high"
},
{
"step": 2,
"text": "Simmer until the beef is tender...",
"duration_minutes": 90,
"temperature": { "value": 95, "unit": "c" },
"heat_level": null
}
],
"ai_generated": true,
"api_version": "v1",
"provenance": { "text": "ai_generated", "ingredients": "ai_generated_db_resolved", "nutrition": "database_computed", "image": "ai_generated" }
}
Field types worth knowing.
allergen_declaration.declarationis an object (contains,may_containwith areasonfor each,hidden_sources), never a sentence, ornull. Render your own wording from it.ingredients[].quantityis a number (fractions as decimals, such as0.5),""when the line gives no amount, ornullfor an unresolved line. It is never0for an unknown amount.ingredients[].gram_weightis the edible grams the nutrition is computed on;purchase_gram_weightis the mass of the quantity as written, to buy (bone-in, shell-on, undrained), and equalsgram_weightwhen nothing is removed before eating. Use it for shopping lists.ingredients[].nutrition_estimatedistruewhen that line’s nutrition rests on an AI estimate, because no open food-composition table carries the food,falsewhen it rests on table values, andnullwhen it cannot be told.provenance.nutrition_estimated_linescounts the estimated lines of the recipe.steps[].temperatureis{value, unit}(unitiscorf; a range addsmax_value) ornull, and a freezer temperature keeps its sign (-18); a heat named in words such asmedium-high(or several, joined in order) is inheat_level, never turned into a number, andheat_levelisnullwhen the step states degrees or no heat.storageis{fridge_days, freezer_days, instructions}: days in the fridge, days in the freezer (nullwhen the dish does not freeze) and how to store and reheat it;{}when the recipe has no storage guidance.taste_profilescores each of the six tastes from 1 to 10.- Nutrient values can be
nullwhen a value is unknown, which is different from0. nutrition.per_100ghas the same eight keys as the per-serving values (calories,protein_g,fat_g,saturated_fat_g,carbohydrate_g,fiber_g,sugars_total_g,sodium_mg), per 100 g of the dish, each a number ornullwhen the data does not state it, never0.cuisine_codeis the ISO 3166-1 alpha-2 code of the cuisine’s country, ornullwhen the cuisine has no code; never an empty string.
Understanding the time fields
Some dishes need an unattended wait, such as an overnight marinade, a proving dough, a week-long pickle. One number cannot honestly describe both how long you stand in the kitchen and when the dish will be ready, so we return both.
| Field | Meaning |
|---|---|
| prep_time_minutes | Hands-on preparation. |
| cook_time_minutes | All cooking time, attended or not: a two-hour oven roast counts in full. |
| rest_time_minutes | The unattended wait: marinating, chilling, proving, fermenting, curing. You are not in the kitchen for this. |
| active_time_minutes | Prep plus the cooking someone has to attend: never more than prep + cook, and less when part of the cooking runs on its own (an oven roast, a covered simmer), as it does for about a third of the catalogue. The figure to show on a card as “35 min”, and what max_active_minutes filters on. |
| total_time_minutes | prep + cook + rest. The wall clock, always. This is what max_time_minutes, min_time_minutes and sort=time_asc operate on, so a 30-minute query can never return a dish that needs an overnight soak. |
| requires_advance_preparation | true when rest_time_minutes is 4 hours or more, i.e. the dish cannot be started and finished in one sitting. Use it for a “start the day before” badge. |
| untimed_minutes | Minutes in the total that no step times: preparing the listed ingredients (washing, peeling, chopping what the ingredient list asks for) and a wait a step only implies. 0 when the steps time every minute. |
| api_version | The API version the recipe was served under ("v1", the same as the X-API-Version header), on every recipe from every endpoint, so a stored recipe still says which contract it follows. Always returned, even when fields trims the recipe. |
| overlap_minutes | Minutes of step time that run alongside other steps (“meanwhile”, “while it bakes”), counted once in the total. |
prep + cook + rest === total holds for every recipe, so does
total === sum(steps[].duration_minutes) + untimed_minutes - overlap_minutes, active ≤ prep + cook, and
active never includes rest. Publishers split this two ways and
both work from these fields: AllRecipes and King Arthur show a separate
“additional time” or “hands-on time” alongside the total, while
NYT Cooking and BBC Good Food write “45 minutes, plus 8 hours’
marinating”.
// A 7-day pickle: honest total, usable headline { "prep_time_minutes": 10, "cook_time_minutes": 18, "rest_time_minutes": 10080, "active_time_minutes": 28, "total_time_minutes": 10108, "requires_advance_preparation": true } // render as: "28 min active, plus 7 days pickling"
Difficulty, cost level, occasions and seasons
These four fields carry a value only when the recipe’s own data supports it. When it does not, the
key is still there and says so: null for difficulty and cost_level,
an empty array for occasions and seasons. Treat both as “not known”,
never as “none” or “average”. A filter on one of these fields returns exactly the
recipes served with the value you asked for.
| Field | Served value | When unknown |
|---|---|---|
| difficulty | Easy, Medium, Hard or Expert: the generation model’s estimate for the dish, not a measurement of the steps. Medium is reserved for a genuinely assessed difficulty. Our generator also wrote Medium whenever it had no judgement, and stored recipes do not record which Medium was assessed, so an unproven stored Medium serves null and no recipe serves Medium today. Handle all four values and null. | null |
| cost_level | 1 (budget) to 4 (luxury): the generation model’s estimate of what the ingredients cost, not a price. A 2 is served only when we recorded that the model gave it, because 2 was also the value filled in when it gave none. | null |
| occasions | Weeknight when total_time_minutes is 30 or less, waits included. Holiday, Party, Family Meal and Meal Prep from the recipe’s own title, description and tags. Everyday is never served. | [] |
| seasons | Spring, Summer, Fall, Winter from the recipe’s own title words, seasonal dish names and season tags. Year-Round is never served: it meant that no seasonal evidence was found, not that the dish suits every season. | [] |
Paid Fields
Two recipe fields are included from the Indie plan: nutrimetric (the A to F NutriMetric grade) and beverage_pairing. They appear on every full recipe object, which every endpoint that returns recipes serves (the list), and beverage_pairing also on /api/recipes/:id/beverages.
| Who is calling | What the paid fields contain |
|---|---|
| Indie, Pro and Enterprise keys | The values, as in the Response Schema. No field_plans. |
| Hobby keys, in your app | A locked marker in place of each value (below). Never null and never {}, so your app can tell “not on this plan” apart from missing data. |
| The Test Kitchen, on every plan | The full values, plus a field_plans object naming the plan that includes each paid field the object carries, so you can see everything each plan returns before you choose one. |
{
"id": "a1b2c3d4-e5f6-...",
"title": "Caldo de Res",
...
"nutrimetric": {
"locked": true,
"available_on": "indie",
"message": "Available on the Indie plan and above"
},
"beverage_pairing": {
"locked": true,
"available_on": "indie",
"message": "Available on the Indie plan and above"
}
}
{
"id": "a1b2c3d4-e5f6-...",
"title": "Caldo de Res",
...
"nutrimetric": { "grade": "B", "score": 68, ... },
"beverage_pairing": { "wine": [ ... ], ... },
"field_plans": {
"nutrimetric": "indie",
"beverage_pairing": "indie"
}
}
Check locked === true before reading a paid field, and show your own upgrade hint or hide the element. available_on is the lowest plan that includes the field and message is ready to display. field_plans only appears in Test Kitchen responses, so production code should not depend on it.
Trimming the Recipe: fields
A recipe is the same object wherever it comes from. Every endpoint that returns recipes returns the complete recipe that /api/recipes/details returns, with its ingredients, steps, nutrition, allergens, storage and provenance, every field listed in the Response Schema. The paid fields follow your plan everywhere, as described in Paid Fields.
/api/recipes,/api/recipes/details(and/api/recipes/:id),/api/random,/api/similarand/api/bulk-lookup- Every
/api/recipes/by-*browse endpoint and/api/recipes/filter:recipes[] /api/search: each item ofdata[]is the recipe plussimilarity(0 to 1, four decimals,nullontext_fallback)/api/ingredients/:id/recipes:recipes[]/api/recognize/image(data.matched_recipes[]) and/api/recognize/ingredients(data.suggested_recipes[])
When a list only needs a few fields, name them with fields and every recipe comes back with just those.
| Parameter | Type | Description |
|---|---|---|
| fields | CSV | Top-level recipe fields to return, comma-separated, for example fields=title,ingredients,nutrition. Omit it for the whole recipe. id, ai_generated and api_version are always returned: ai_generated is the EU AI Act Art. 50(2) disclosure and cannot be trimmed, and api_version names the API version the recipe was served under. Accepted names: id, title, title_english, description, image_url, prep_time_minutes, cook_time_minutes, rest_time_minutes, total_time_minutes, active_time_minutes, requires_advance_preparation, untimed_minutes, overlap_minutes, servings, cuisine, cuisine_code, meal_types, dish_types, difficulty, cost_level, protein_source, tags, cooking_methods, occasions, seasons, equipment, dietary_tags, allergens, allergen_declaration, nutrition, taste_profile, storage, nutrimetric, beverage_pairing, ingredients, steps, ai_generated, api_version, provenance. /api/search also accepts similarity. On the two recognize endpoints, send it on the query string (/api/recognize/image?fields=title,nutrition); it is checked before the image is analysed, so a typo costs nothing. |
curl -X GET "https://api.foodashi.com/api/recipes/by-cuisine?cuisine=Italian&limit=2&fields=title,ingredients,nutrition" \ -H "X-Api-Key: YOUR_API_KEY" { "recipes": [ { "id": "a1b2c3d4-e5f6-...", "ai_generated": true, "api_version": "v1", "title": "Spaghetti alla Carbonara", "ingredients": [ ... ], "nutrition": { "calories": 520, "protein_g": 22, ... } } ], "filter": { "cuisine": "Italian" }, "pagination": { "page": 1, "limit": 2, "total": 245, "total_pages": 123 } }
{
"error": "Unknown recipe field in fields: colour",
"code": "unknown_field",
"unknown_fields": ["colour"],
"accepted_fields": ["id", "title", "title_english", ...]
}
- A list may carry
"partial": trueandfailed_ids: [...]when a recipe could not be formatted: it is named there, never silently dropped. - If the data behind a list cannot be read, the browse endpoints,
/api/recipes/filter,/api/searchand/api/ingredients/:id/recipesanswer503with{"error": "Reference data could not be read, so no partial answer was served. Please retry.", "code": "service_unavailable", "retryable": true}, as the other recipe endpoints do. Retry with backoff. - Coming from the old list cards: two of their fields are not part of the recipe. Read
meal_types[]where you read the singlemeal_type;published_atis no longer returned.
Available Cuisines (81)
East Asia
Southeast Asia
South & Central Asia
Middle East
North Africa
Europe
Nordic
North America
South America
Caribbean
Pacific
Sub-Saharan Africa
Meal Types
Common Allergens (EU 14)
Track and filter recipes based on the 14 EU-regulated food allergens. Each recipe includes both confirmed allergens (detectedAllergens) and uncertain traces (mayContainAllergens).
Kitchen Equipment (125)
Filter recipes by required kitchen equipment. Equipment is auto-detected from recipe instructions across all 81 world cuisines.
Pots & Pans
Bakeware
Appliances
Knives & Cutting
Prep Tools
Measuring
World Cuisine Specialty
Changelog
What's new in the Foodashi API. We ship data-quality and capability improvements continuously.
Call any endpoint as /api/v1/… (unversioned /api/… paths keep working).
Every response returns an X-API-Version header so you always know which contract you received.
Browser JavaScript on an allowed origin can read it too, with the rate limit, monthly allowance, overage, request cost, warning and Retry-After headers: the API lists them in Access-Control-Expose-Headers.
Our compatibility promise: within v1 we only add: we never remove or rename a field,
or change the shape of a response. Anything breaking would ship as /api/v2/, and v1 would keep
running. The releases below are improvements inside v1; most need no change to your code, and seven do. v5.9 is a prelaunch reset of the v1 contract, with breaking changes. Breaking request change: season=Year-Round, occasion=Everyday and difficulty=Medium, which earlier releases accepted, are refused with a 400. Breaking response change: difficulty, cost_level, occasions and seasons serve null or [] when the value is not known, Weeknight means 30 minutes or less in total, and a season filter no longer matches Year-Round recipes. Stop sending those values and treat null and [] as unknown. In v5.6 a locked paid field on a Hobby key became a {"locked": true} object instead of null or {}. In v5.8 a nutrient value the data does not state became null instead of 0 on the ingredient, barcode and ad hoc analysis endpoints, and requests that used to be answered wrongly (an unknown browse parameter, a non-mass unit on ad hoc analysis, meal servings outside 1 to 100) are refused with a 400; a check or read the API could not complete is a retryable 503 instead of a guess. v5.10 withholds Low-FODMAP on every recipe: diet=Low-FODMAP stays valid and matches no recipe, and a meal plan restricted to it is refused with a 400. v5.13 serves nutrition.per_100g with the eight per-serving keys (calories, protein_g and the rest), each a number or null, in place of an object per nutrient, and refuses an off-list browse filter value with a 400 instead of an empty page. v5.14 refuses with a 400 what was silently ignored before: a meal plan diet, allergen or cuisine word that names nothing, an unknown exclude_diets word, an off-list meal type, a shopping-list budget or diet word it does not know, and a parameter /api/recipes/count does not read; it serves /api/recipes/compare in the recipe details shape, and keeps the credits of a nutrition calculate 422 answered after the work. v5.16 serves the complete recipe on every endpoint that returns recipes, so a browse, filter or search result no longer carries the old card’s singular meal_type (read meal_types) or published_at.
No entries match your search.
2026 17 releases
v5.16 Latest 22 October 2026 #
- Improved One recipe on every endpoint. The 14
/api/recipes/by-*browse endpoints,/api/recipes/filter,/api/search,/api/ingredients/:id/recipesand the matches of/api/recognize/imageand/api/recognize/ingredientsreturn the complete recipe that recipe details returns: ingredients, steps, nutrition, allergens, storage, provenance and every time field. They returned a reduced card. The paid fields follow your plan, as on/api/recipes;/api/searchkeeps its envelope and each result addssimilarity. A list names a recipe it could not format infailed_idswith"partial": true, never dropping it silently. Migration: two card fields are not on the recipe: readmeal_types[]where you readmeal_type, andpublished_atis no longer returned. One recipe, every endpoint - Added
fieldson every recipe endpoint:fields=title,ingredients,nutritionreturns only the fields you name, plusid,ai_generatedandapi_version, which always come./api/searchalso acceptssimilarity, and on the recognize endpointsfieldsgoes on the query string and is checked before the image is analysed. A name the recipe does not carry is a400unknown_fieldwithunknown_fieldsandaccepted_fields. Trimming the Recipe - Added
api_versionon every recipe from every endpoint ("v1", the same as theX-API-Versionheader), so a recipe you store still says which API contract it follows.fieldsalways keeps it. - Added
max_active_minuteson every browse endpoint,/api/recipes/by-ingredientsand/api/recipes/filter: keeps recipes whose hands-on time,active_time_minutes, is at most that many minutes. Total time counts waits that need no cook, so 2,887 of the 7,180 published recipes need 30 minutes or less of hands-on work while only 485 are done in 30 minutes total. An invalid value is a400invalid_parameter. Browse conventions - Added By ingredients takes
ingredientsas another name forinclude(sending both is a400invalid_parameter) andmatch=anyfor recipes holding any of the terms, to cook with what you have;match=all, every term, stays the default andfilterechoes it. - Added By nutrients bounds saturated fat and sugars per serving:
minSaturatedFat,maxSaturatedFat,minSugarsandmaxSugars(ormin_saturated_fat_g,max_saturated_fat_g,min_sugar_g,max_sugar_g). Its recipes now keep theirnutrition./api/recipes/filteraddsmax_saturated_fat_g. - Fixed A failed read on the browse endpoints,
/api/recipes/filter,/api/searchand/api/ingredients/:id/recipesanswers the retryable503service_unavailable, as the other recipe endpoints do, instead of a500. Retry with backoff. - Fixed The docs and the OpenAPI document: by time reads
max_time_minutes(default 30;maxMinutesis its old name);storageis{fridge_days, freezer_days, instructions};total_time_minutes(prep + cook + rest, also on thetotal_timefilter) andactive_time_minutesread the same everywhere;steps[].heat_levelis described besidetemperature; and the fiber and sodium bounds of by nutrients are listed.
v5.15 21 October 2026 #
- Added The
Candydish type, for sweets eaten by the piece: fudge, toffee, nougat, turrón, marzipan, truffles, brigadeiros, laddu, barfi, tahini halva. Rice-dough sweets such as mochi stayConfection. It is in the dish type list and thedish_typesfilter; recipes already published keep their current types. - Improved Servings follow how a food is eaten. A hot sauce, chili paste or mustard serves a teaspoon, soy sauce, ketchup or mayonnaise a tablespoon, a dressing, salsa or chutney about 30 g, and a sauce served as the meal’s own sauce about 125 g. Cookies serve about 60 g (two), mezze dips about 60 g, piece sweets about 30 g and cured fish served as a starter about 55 g. Per-serving values follow the new counts; recipe totals do not change.
- Improved A dish cooked in several countries is kept as each country’s own version, so a search can return several kinds of the same dish; a recipe that only copies another one under a different cuisine is not published.
v5.14 21 October 2026 #
- Fixed Meal plans read diet, allergen and cuisine words the way the browse endpoints do:
Milkexcludes recipes that declare Dairy,veganmatches Vegan anditalianmatches Italian. A word that names nothing (an allergen outside the EU 14, an unknown diet or cuisine) is refused with400(invalid_allergen, orinvalid_parameterwithvalid_values) before any plan is made. It used to be ignored, so a plan could include an allergen you asked to exclude. If the recipe read fails, the answer is a retryable503, never a plan made up without recipes. This applies to/api/meal-plan/generateand to regenerating a day or a meal with/api/meal-plan/customize. - Fixed Semantic ingredient search serves the nutrition of every food on the page. Past about the fifteenth food it served
nullvalues andhas_nutrition: false. It also pages past the first candidates: a later page used to come back empty. A failed read is a retryable503. - Fixed
exclude_dietsmatches a diet in any letter case, and an unknown diet is refused with400instead of being ignored./api/recipes/countreadsdifficultyin any case, now also readsseasonandoccasion, and refuses a parameter it does not read with400unknown_parameter, as every other list endpoint does./api/recipes/by-meal-typeaccepts any letter case and refuses an off-list type with400. - Fixed
/api/recipes/by-ingredients,/api/ingredients/by-food-group,/api/ingredients/:id/recipesand/api/ingredients/by-nutrientfollow the shared paging rules:offsetis read,random=truesamples, and past offset 1000 the answer is400offset_too_deep. A food group containing%or_is matched as written. - Added Swiss cuisine:
cuisine=Swiss(codeCH, Europe) is the catalogue’s 81st cuisine, in the cuisine list and/api/cuisines. - Fixed
/api/searchcounts each call once against your per-minute limit. It counted twice, so you reached the limit at half your plan's rate. - Fixed A request refused as too large (
413) costs nothing, like every other refused call. - Fixed
Authorization: Beareraccepts every key format the developer console issues. - Improved Every
429body carrieslimit_scope:plan(your plan’s per-minute limit),endpoint(an AI endpoint’s own per-minute cap, the same on every plan, now named in the message) oraccount(console calls). A burst far above any plan’s limit is refused with429rate_limitedbefore any work, and costs nothing. - Fixed If the ingredient data cannot be read,
/api/nutrition/calculate,/api/nutrition/log,/api/analyze/textand/api/analyze/urlanswer the retryable503. They used to report the foods as unmatched./api/analyze/textanswers422when the text holds no ingredients, as/api/analyze/urldoes (it answered500). - Breaking Response change.
/api/recipes/compareserves each recipe’snutritionandnutrimetricin the same shape as recipe details: the eight nutrients,net_carbs_g,daily_value_percent,caloric_breakdownand the flatper_100g;nutrimetricis{grade, score, label, version}ornull. The raw stored objects (per_serving,total,caps_applied, the oldper_100gshape, and the grade’s internalbreakdown,categoryandcapped_by) are no longer served. - Fixed A read that fails answers the retryable
503service_unavailableeverywhere: it used to answer404on recipe details, every recipe sub-resource,/api/similar,/api/randomand the ingredient endpoints by id, and500on the lists, bulk lookup, autocomplete, every browse endpoint, compare and the shopping lists. A recipe or ingredient that does not exist is still404. - Fixed A nutrient the data does not state is
null, never0, in the served recipe nutrition;net_carbs_gand the daily value percentages follow. No published recipe changes today: every one states all eight values. - Added
/api/recipes/by-countryservesfilter.cuisines_in_country, as documented, besidefilter.cuisines. - Fixed
/api/shopping-list/optimizereadspreferences.budgetaslow,normalorpremium(andtight,normal,flexible); any other budget or diet is refused with400andvalid_valuesbefore the AI step. It used to fall back tonormalsilently. Anullor array JSON body on a POST endpoint is400invalid_body, a non-numeric autocompletelimitis400, and a blank name in an ingredient batch is400, where they answered500or an arbitrary food. - Breaking Billing change.
/api/nutrition/calculatekeeps its credits on a422it answers after matching your ingredients, because that work is done; a400before any work stays free.
v5.13 20 October 2026 #
- Breaking Response change.
nutrition.per_100gon every full recipe object (/api/recipes,/api/recipes/details,/api/random,/api/bulk-lookup,/api/similar) now has the same eight keys as the per-serving values:calories,protein_g,fat_g,saturated_fat_g,carbohydrate_g,fiber_g,sugars_total_gandsodium_mg, each a number ornullwhen the data does not state it, never0, as the Response Schema documents. It used to serve one object per nutrient keyed by nutrient code ("energy_kcal": {"name": "Energy", "unit": "kcal", "value": 214.11}), with an extraalcoholkey. Recipe nutrition now serves it too, asnutrition_per_serving.per_100g; a?servings=override does not change it. Shape change: readper_100g.calorieswhere you readper_100g.energy_kcal.value. - Fixed A page past the last one, on any paged list (
/api/recipes,/api/recipes/filter, the/api/recipes/by-*endpoints,/api/ingredients/:id/recipes,/api/ingredients/by-food-group,/api/ingredients/by-nutrient), returns200with an empty list and the truepagination.totalandtotal_pages. It used to answer500. Past offset 1000 is still400offset_too_deep. - Fixed A filter value off the accepted list is refused with
400invalid_parameterand the accepted values, instead of an empty200that looked like “no recipes”: by-diet (valid_diets), by-cooking-method, by-season and by-occasion (valid_values), thecooking_methodfilter of/api/recipes/filter, ingredient searchtypeand meal plan templatesgoal(valid_values). - Fixed By cooking method accepts the everyday word for a method:
method=bakingfinds the baked recipes (it found none), and so dogrilling,frying,steaming,roasting,boiling,braisingand more.smoking(cold smoke) andsmoked(hot smoke) stay two different methods. - Fixed Unit converter: a unit it does not recognise, on either side, is a
400unknown_unitwith ahintand thesupported_units, instead of a guessed 30 g. A piece unit needs aningredientwhose piece weight is known (ingredient=egg).fl oz,pint,quartandgallonfollow the ingredient’s density, large amounts are converted in full (60 cups no longer stops at 5,000 g), and every answer carriesgrams, also withto=g. - Fixed Ingredient unit conversion: an unknown unit is a
400unknown_unit; a known unit this food has no portion or density data for stays a404no_conversion_data. Both listavailable_units. Both used to be a404. - Fixed
cuisine_codeisnullwhen the cuisine has no code, never an empty string. - Fixed Extra usage takes over the moment your included requests run out, mid-burst and on every server, and your included requests come back first when the month resets. With automatic top-up on, the card is charged as soon as the balance drops under your threshold, so no call has to be refused first; while a top-up is on its way,
overage_depletedcarriesRetry-After: 60instead of the time to the monthly reset. - Added
429 overage_limit_reached: the monthly extra usage spending limit you set is reached. It used to read asoverage_depletedalthough credit was left on your balance. Errors - Fixed A call answered
402or405gives back what it counted, like every other failed call. What a failed call costs - Fixed /stats:
total_dietsandtotal_cuisinescount the lists/api/dietsand/api/cuisinesserve (21 and 80); they read 9 and 54. - Fixed Ingredients by nutrient no longer times out with a
500on common nutrients such asvitamin_c. - Fixed A barcode with letters in it answers
400invalid_barcode, as documented, instead of a404. - Fixed Recipes with an ingredient counts every published recipe that uses it. A common ingredient was cut short: egg, in 1,517 recipes, reported 302. An unknown ingredient id answers
404not_found, as its other endpoints do, instead of an empty200. - Fixed
cuisine_codefor a regional cuisine is its country’s code (CantoneseisCN,IsanisTH,CatalanisES). A cuisine that spans several countries (Middle Eastern,Caribbean,Levantine) isnull; it used to carry one country’s code. - Fixed Recipe numbers: a deep-fried dish no longer counts frying fat on side dishes and sauces that never go into the oil (13 recipes, less fat); a step whose phases run one after another is timed as their sum (819 recipes, a few minutes longer on most);
protein_sourcelists an animal only when an ingredient of that animal backs it, never another animal’s organ, broth or fat (21 recipes); and saturated fat, fiber and sugars missing from a source food are filled from that source’s own table (118 recipes). - Fixed The docs and the OpenAPI document now describe what these endpoints serve: /stats (totals only, now in the OpenAPI document), ingredient substitutes, autocomplete (matched anywhere in the title; a missing or one-character
qis an empty200), ingredient detail portions (unit_name,gram_weight), the recipenutrimetric(grade,score,label,version), paging values out of range (clamped, never refused) and compare, whose answer is not in the order ofids.
v5.12 19 October 2026 #
- Breaking Response change.
protein_sourcenames goat asgoat. 50 recipes made with goat (curry goat, chivo, cabrito, kambing, South Asian mutton curries) servedlamband now servegoat. If your app maps protein sources to a fixed list, addgoat. Shape change: none; one new value. - Fixed A dish made with insect larvae or grubs is never tagged
VeganorVegetarian. - Fixed Nutrition no longer counts water a recipe soaks a food in and then drains, nor salt used to purge or clean shellfish and then rinsed away. 6 recipes were recalculated.
v5.11 19 October 2026 #
- Added
nutrition_estimatedon every ingredient line:truewhen the line’s nutrition rests on an AI estimate, because no open food-composition table carries the food,falsewhen it rests on table values,nullwhen it cannot be told.provenance.nutrition_estimated_linescounts them. Additive, no version change. - Fixed Ingredient nutrition now follows the official food-composition tables it is drawn from: 686 ingredients were corrected to their table’s figures, and the 388 recipes using them were recalculated.
- Fixed Sodium on boiled pasta and dumplings no longer counts the salt poured away with the cooking water.
v5.10 15 October 2026 #
- Breaking Response change.
Low-FODMAPis withheld on every recipe. A low-FODMAP rating depends on the amount of each food per serving and on how FODMAPs add up across the meal; we hold no sourced per-serving FODMAP data, so we no longer make the claim.dietary_tagsno longer carries it on any response (the full recipe, cards, search, medical, compare and meal plans), and /api/recipes/diets no longer lists it. Every other tag is unchanged. Shape change: none; the same keys, one value fewer. - Breaking Request change.
diet=Low-FODMAPstays a valid value. It answers200with no recipes andwithheld_diets: [{"diet": "Low-FODMAP", "reason": "..."}]on by-diet, every browse endpoint,/api/recipes,/api/recipes/countand search; random answers404with the same field.exclude_diets=Low-FODMAPexcludes nothing. A meal plan with"Low-FODMAP"indietary_restrictionsis refused with400 invalid_parameterand areason. - Added
withheld_diets, each withheld diet with its reason, on a response whose diet filter names one, and on /api/recipes/diets and /api/diets, whose entries also carrywithheld. Additive.
v5.9 14 October 2026 #
- Breaking Response change.
difficulty,cost_level,occasionsandseasonscarry a value only when the recipe’s own data supports it, and say “not known” otherwise:nullfor the first two,[]for the arrays, with every key kept. A Medium difficulty, a cost level of 2 we cannot trace to the model, the Everyday occasion and the Year-Round season are no longer served, because each was also the value written when nothing was known.Mediumstays in the response type as a valid difficulty, reserved for a genuinely assessed difficulty; an unproven stored Medium servesnull.Weeknightnow means the whole recipe, waits included, takes 30 minutes or less, and seasons now include the recipe’s own season tags. What these fields mean. Shape change: treatnulland[]as unknown. - Breaking Request change. Season, occasion, difficulty and cost filters return exactly the recipes served with the value you asked for.
season=Springno longer returns every Year-Round recipe (nearly the whole catalogue).season=Year-Round,occasion=Everydayanddifficulty=Mediumare refused with400 invalid_parameter, areasonand thevalid_values, on every browse endpoint,/api/recipes,/api/recipes/countand meal plans.cost_level_maxandcost_tierleave out recipes whose cost level is not known.
v5.8 13 October 2026 #
- Added
purchase_gram_weighton every ingredient line: the mass to buy (bone-in, shell-on, undrained), besidegram_weight, the edible grams the nutrition is computed on. Scale lines addscaled_purchase_gram_weight. Additive, no version change. - Fixed The OpenAPI document and the examples now describe the recipe object exactly as served:
allergen_declaration.declarationis an object, ingredient quantities are numbers,steps[].temperatureis{value, unit}ornullwithheat_levelbeside it, taste scores run 1 to 10, and unknown values are typed asnull(OpenAPI 3.1). Clients generated from the old document may need regenerating. - Fixed When the data behind an answer cannot be read, the API now answers
503with codeservice_unavailableand"retryable": trueinstead of a partial or empty success: recipe lists, details, bulk lookup, similar, a single random recipe and by-ingredients, the three shopping list endpoints, ingredient search, details and batch, ad hoc analysis and nutrition calculate. Retry the request. Random withcountabove 1 leaves out a recipe that cannot be read and says so with"partial": trueandfailed_ids. - Fixed When your account’s quota or extra-usage check cannot answer, every metered endpoint now answers
503with codeservice_unavailableand"retryable": trueand serves nothing. Such a call used to be served without being counted, or refused as over quota. Retry the request. Errors - Fixed
429 overage_depletednow means your prepaid extra usage really cannot pay for the call. A balance check that does not answer is a503, and it no longer starts an automatic top-up. - Fixed Too many API key lookups from one client address (120 per minute) now answer
429 rate_limitedwith"limit": 120and aRetry-Afterin seconds. They used to answerquota_exceededwith aRetry-Afterat the end of the month. Rate limits - Fixed By ingredients:
exclude=eggnow leaves out every recipe that holds egg; it used to miss most of them. A term that cannot be read is a503. - Fixed Analyze text, analyze URL and meal logging answer
503service_unavailablewhen a nutrient read fails after the AI step, instead of totals that leave the food out or count it as zero. - Improved Food recognition: when the recipe lookup fails after a dish is identified, the answer keeps the identification and says
"partial": truewitherrors, so an emptymatched_recipeson its own always means no recipe matched. Dish names written with vowel signs, such as Devanagari, Thai, Tamil or Bengali, now find their recipes. - Improved Nutrition calculate reads each line’s optional
preparation: butter with"unsalted, softened"is costed as unsalted butter. - Fixed A nutrient value the data does not state is now
null, never0: ingredient details and batch (nutrition_per_100g), barcode (nutrition, each gap named innutrition_gaps) and ad hoc analysis (each line and every total it enters). A measured0stays0. Shape change: if your code adds these values up, treatnullas unknown. - Fixed Every
/api/recipes/by-*endpoint now readsexclude_allergens,exclude_algandallergenalike, and refuses a parameter name it does not read with400 unknown_parameter(listingaccepted_parameters). A misspelt or alias allergen filter used to be ignored, so the page kept the recipes it should have excluded./api/recipes/by-ingredientsnow applies the shared browse filters too. Browse conventions - Fixed Ad hoc analysis weighs mass units only (
g,kg,mg,lb,ozand their spellings). A volume or piece unit, or an amount that is not a number from 0, is refused with400 invalid_parameter; any other unit used to be read as grams, so 2 cups weighed 2 g and 500 mg weighed 500 g. - Fixed Shopping lists from a meal plan buy for every meal: a meal without
servingscounts the recipe's own servings, and totals are never capped. Meal servings must be 1 to 100, or the request is refused with400 invalid_parameter. - Fixed Daily needs refuses inputs outside what its equation describes with a
400instead of a negative or empty target, and nutrition calculate answers422when no matched food has nutrient data instead of a200with empty figures. - Improved Sustainability: milk and white chocolate have their own food class,
milk or white chocolate, counted as animal-based (milk chocolate used the factor of plain milk, and white chocolate counted as plant-based). Dark chocolate that states its cocoa content now uses the chocolate factor instead of the cocoa powder one. - Improved Browser code on an allowed origin can send
Authorization: Bearerand readX-API-Version, theX-RateLimit-*andX-Monthly-*headers,X-Overage-Active,X-Request-Cost,X-Foodashi-Warning,X-Edge-CacheandRetry-After. - Fixed A step temperature below zero, such as a freezer at -18°C, keeps its minus sign in
steps[].temperature.
v5.7 7 October 2026 #
- Improved Dishes cooked with wine, beer or spirits now count only the alcohol that survives the cooking, worked out from how long the recipe cooks it after the drink goes in (USDA retention factors). A long wine braise no longer carries all of the wine’s alcohol energy.
- Improved An ingredient listed without an amount, such as sugar for dusting, flour for the work surface or oil for greasing and drizzling, now adds a typical amount to the recipe’s nutrition instead of nothing.
- Improved An ingredient described as pitted or unsalted now uses nutrition for exactly that form, such as stoneless prunes or unsalted butter.
- Improved Allergen warnings cover more bought ingredients: miso warns for gluten, generic stock warns for celery, and blends such as asafoetida, dashi powder and fermented bean pastes carry the warnings their common commercial forms need. A free-from label is withheld when an ingredient’s allergens cannot be confirmed.
- Improved Nutrition claims and diet tags such as Low-Fat, Low-Sodium and High-Protein are awarded only when a recipe sits comfortably inside the limit, allowing for the natural variation of a calculated recipe, not when it only just scrapes past it.
- Improved
equipmentnow lists every tool the steps use, such as a whisk, a wire rack or a slotted spoon, and drops listed tools nothing uses. Tool names are written one way everywhere, so the equipment filter finds every recipe that needs a tool. - Improved Storage advice always sends cooked moist dishes such as soups, stews, curries and bean dishes to the fridge.
- Improved
beverage_pairingalways carries the same six lists (wine,beer,cocktails,non_alcoholic,spirits,recommendations), empty when there is nothing of that kind, or isnullwhen a recipe has no pairing. - Added
untimed_minutesandoverlap_minuteson every recipe: the total now adds up from the steps,total_time_minutes = sum(steps[].duration_minutes) + untimed_minutes - overlap_minutes, exactly, so you can show or check where every minute goes. - Improved A step whose text states a temperature (“until it reaches 70°C”, “heat the oil to 180°C (350°F)”) now serves it in
temperatureas{value, unit}instead ofnull. - Improved Prep, cook and rest times follow what each step does: a step that opens with a condition (“once cool”, “just before serving”) and then works is counted as work, not waiting. Rich desserts that served a whole pot to four people are portioned realistically.
v5.6 30 September 2026 #
- Added
POST /api/v1/recipes/{id}/reports: let your users report a recipe that looks wrong, such as a missing allergen, a wrong diet tag or a wrong step. Free on every plan: 0 credits, and it works even when your key is over its monthly quota. Allergen and diet tag reports reach the Foodashi team straight away. - Improved A paid field your plan does not include now says which plan unlocks it. Shape change for Hobby keys:
nutrimetricandbeverage_pairingare now{"locked": true, "available_on": "indie", "message": "..."}instead ofnullor{}. If your Hobby app checked fornull, checklockedinstead. Paid plans are unchanged. Paid Fields - Improved The Test Kitchen now shows every field on every plan, with a
field_plansobject naming the plan that includes each paid field, so you can see exactly what each plan returns before choosing one. - Improved Sustainability now returns the recipe's climate footprint in kg CO2 equivalent per serving and per recipe, with the plant-based share, the top contributing ingredients and the estimate's coverage. Shape change: the endpoint no longer repeats the NutriMetric grade and the diet tags, which are on the recipe itself.
- Added
POST /api/analyze/textand/api/analyze/urlnow return the climate footprint of the recipe you send, in the samesustainabilityshape. No extra credits. - Improved Every recipe sub-resource (nutrition, taste, allergens, sustainability, beverages, medical, scale) now includes
title_englishbeside the nativetitle, and answers errors with acode(invalid_id,not_found) like the full recipe. - Added Attribution: the Hobby plan requires a visible “Powered by Foodashi” credit linking to foodashi.com wherever Foodashi data is shown, with a copy-paste snippet. Paid plans need no Foodashi credit.
v5.5 September 2026 #
- Added Allergen recognition in many more languages: native ingredient names from our 80 cuisines are now matched to the 14 major allergens defined by EU law.
- Improved Engine update 2026.09: every nutrition value now comes from one unified calculation, identical when a recipe is created and whenever it is updated, so the numbers match across every endpoint.
- Improved More accurate ingredient weights, based on USDA reference portions for cups, spoons and whole pieces of each named food.
- Improved Serving sizes follow the dish: breads, desserts, pastries, snacks and batch recipes are now portioned realistically.
- Improved Diet claims such as Low-Fat, Low-Sodium and Low-Carb are shown only when the whole recipe supports them.
- Improved Cooking times are more precise: prep, cook and total time are measured from the method, and overnight soaking or marinating is counted once.
- Improved Fried, soaked and cured dishes: absorbed frying oil and salt removed by soaking are now modelled more accurately.
- Improved Stability and consistency improvements across the recipe, nutrition and search endpoints. Existing recipes are being brought up to the new engine progressively.
v5.4 July 2026 #
- Added New endpoint
POST /api/nutrition/calculate: send a structured ingredient list and get full per-serving, per-100g and total nutrition plus a NutriMetric grade. Deterministic and AI-free, so it is fast, repeatable and costs 5 calls. Indie and above. - Improved Broader EU-14 allergen coverage for international cooking: ingredients such as dashi, gochujang, za’atar, prahok, kecap manis and belacan are now recognised by name, along with hidden-source terms like casein, semolina and gingelly.
- Improved Allergen declarations, dietary tags and the EU compliance block are now reconciled end-to-end, so every recipe tells one consistent story.
- Improved Sharper crustacean and mollusc separation, matching the two distinct Annex II categories.
- Improved Higher nutrition precision: energy values cross-checked against source data, and per-serving figures verified across the catalogue.
- Improved NutriMetric grades now move in lockstep with the nutrition they score.
v5.3 June 2026 #
- Improved More accurate nutrition: gram-weighted nutrient coverage and consistent per-serving math across the catalog.
- Added Two-axis taxonomy on every recipe:
dish_types[](what a dish is) andmeal_types[](when it's eaten). - Improved Higher recipe authenticity: canonical-dish fidelity checks at generation and native dish names.
- Improved Stronger EU-14 allergen and dietary-tag accuracy, with confirmed vs. may-contain flags.
v5.2 March 2026 #
- Added AI Vision endpoints: Recognize Dish and Recognize Ingredients (Pro).
- Added Beverage Pairing AI Sommelier endpoint.
- Improved Variable endpoint costs (AI endpoints cost 3 to 25 credits) and clearer error messages with debug hints.
v5.1 February 2026 #
- Added Meal Planning (Generate, Customize, Templates) and Shopping List generation.
- Added NutriMetric scoring system (A to F nutrition grades).
- Improved Overage billing for Indie/Pro tiers.
v5.0 January 2026 #
- Added Vector-powered semantic search (Smart Search) and 70+ endpoints across 11 categories.
- Improved Complete API rewrite on Cloudflare Workers (5× faster).
- Added Tier-based access control (Hobby / Indie / Pro).