460+ recipes (available in Portuguese and UK English), 290+ ingredients with locale names, real-time supermarket prices, wine & beer pairings, chef-curated content, and meal planning. The data infrastructure Bourmet runs internally — now exposed.
A single REST API exposing the data that powers bourmet.pt and bourmet.co.uk.
One key, two markets. Switch with ?locale=pt or ?locale=en-gb. Recipes, ingredients, supermarkets and prices all locale-scoped.
Profiles, recipes, sponsor partnerships from real working chefs. Currently live: Hélio Loureiro (30 recipes, full bio, social links).
Continente, Auchan, Pingo Doce, Aldi (PT) plus Tesco, Sainsbury's, Asda, Lidl, Waitrose (UK). Normalized per gram/ml. 100-day price history.
Generate optimized weekly plans by budget, servings, difficulty and cooking mode. Swap recipes in place. Shareable via short token.
Curated wine and beer types matched to recipes. Sommelier-style pairings without paying a sommelier.
Swap suggestions with ratios (vegan, gluten-free, allergy-aware) and unit conversions (ml ↔ g ↔ cup) per ingredient.
Recent shipped work — April 2026.
time_minutes; total time separates active cooking from passive rest.recipe_links; UK supermarkets & prices populated.All endpoints require an API key via the X-API-Key header. Responses are JSON with UTF-8 encoding.
curl -H "X-API-Key: YOUR_API_KEY" https://bourmet.dev/v1/recipes
API keys are tied to a locale (pt or en-gb). The locale determines which data you receive by default. Override with ?locale=en-gb.
| Header | Description |
|---|---|
X-RateLimit-Limit | Daily request limit |
X-RateLimit-Remaining | Requests remaining today |
X-RateLimit-Reset | Unix timestamp when limit resets |
X-Plan | Your plan name |
460+ recipes available in Portuguese and UK English (some UK translations still in progress) with ingredients, step-by-step instructions, cooking modes, pairings, FAQs, and ratings.
Paginated recipe list with sorting and filters. Returns 20 items per page by default.
GET /v1/recipes/index
GET /v1/recipes/index?page=2&per_page=10
GET /v1/recipes/index?category=peixe&sort=rating&order=desc
GET /v1/recipes/index?search=bacalhau&max_time=30
GET /v1/recipes/index?mode=kitchen-robot&difficulty=easy
GET /v1/recipes/index?ingredients=1,25,14
| Param | Default | Description |
|---|---|---|
page | 1 | Page number |
per_page | 20 | Items per page (max 100) |
| Param | Default | Options |
|---|---|---|
sort | date | date, title, difficulty, time, cost, rating, calories |
order | desc | asc, desc |
| Param | Example | Description |
|---|---|---|
category | peixe | Filter by category slug |
difficulty | easy | easy, medium, hard |
mode | kitchen-robot | kitchen-robot, airfryer, mc |
meal | dinner | breakfast, lunch, dinner, snack |
min_time | 30 | Minimum total_time in minutes |
max_time | 60 | Maximum total_time in minutes |
max_cost | 3.00 | Maximum cost_per_serving |
search | bacalhau | Search in title and description |
ingredients | 1,25,14 | Recipes containing any of these ingredient IDs |
nutrition | high-protein | high-protein (≥25g), low-carb (≤15g), light (≤250cal), low-cal (≤300cal) |
mood | comfort | comfort, quick-and-easy, impress, family, traditional, kids |
{
"data": [
{
"id": "147",
"slug": "bacalhau-a-bras",
"title": "Bacalhau à Brás",
"image": "147",
"total_time": "35",
"difficulty": "easy",
"servings": "4",
"cost_per_serving": "2.06",
"calories": "420",
"date": "2026-02-24",
"categories": ["peixe"],
"tags": ["bacalhau", "rapido"],
"meals": ["lunch", "dinner"],
"modes": ["kitchen-robot"],
"ingredient_ids": [25, 51, 14, 3, 7, 1, 5, 9, 70, 56]
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 460,
"total_pages": 23,
"has_next": true,
"has_prev": false
}
}
Returns counts for all filterable dimensions — categories, modes, meals, difficulty, nutrition labels, and time ranges. Use this to populate filter UIs without loading recipes.
GET /v1/recipes/facets
{
"total": 460,
"categories": [
{"slug": "carne", "label": "Carne", "count": "116"},
{"slug": "peixe", "label": "Peixe e Marisco", "count": "98"}
],
"modes": [
{"mode": "kitchen-robot", "count": "134", "label": "Bimby"},
{"mode": "airfryer", "count": "130", "label": "Airfryer"},
{"mode": "mc", "count": "45", "label": "Mc"}
],
"meals": [
{"meal": "dinner", "count": "320"},
{"meal": "lunch", "count": "280"}
],
"difficulty": {"easy": 289, "medium": 98, "hard": 19},
"nutrition": {
"high_protein": 45,
"low_carb": 32,
"light": 28,
"low_cal": 67
},
"time_ranges": {
"under_15": 25,
"under_30": 89,
"under_60": 245,
"over_60": 161
},
"moods": {
"comfort": 85,
"quick_and_easy": 89,
"impress": 32,
"family": 45,
"traditional": 120,
"kids": 28
}
}
kitchen-robot shows as "Bimby" (PT) or "Thermomix" (UK) based on locale.
Returns complete recipe data including steps, ingredients with localized names, cooking modes, pairings, FAQs, and ratings.
GET /v1/recipes/bacalhau-a-bras
{
"id": "147",
"locale": "pt",
"slug": "bacalhau-a-bras",
"title": "Bacalhau à Brás",
"description": "O clássico português de bacalhau desfiado...",
"image": "147",
"prep_time": "15",
"cook_time": "20",
"total_time": "35",
"servings": "4",
"difficulty": "easy",
"seasons": "all-year",
"cost_per_serving": "2.06",
"calories": "420",
"protein": "28",
"carbs": "22",
"fat": "24",
"steps": [
{
"mode": "normal",
"step_order": "0",
"text": "Desfie o bacalhau em lascas finas...",
"time_minutes": null,
"tip": "Compre bacalhau já demolhado..."
}
],
"ingredients": [
{
"ingredient_id": "25",
"name": "bacalhau desfiado e demolhado",
"ingredient_slug": "bacalhau-desfiado-e-demolhado",
"qty": "400.00",
"unit": "g",
"scalable": "1"
}
],
"categories": [{"slug": "peixe", "label": "Peixe e Marisco"}],
"tags": ["bacalhau", "rapido", "familia"],
"meals": ["lunch", "dinner"],
"modes": [
{"mode": "kitchen-robot", "note": "...", "description": "..."}
],
"pairings": [...],
"pairing_types": [
{"category": "wine", "type_key": "vinho_branco"},
{"category": "beer", "type_key": "weiss"}
],
"faqs": [{"question": "...", "answer": "..."}],
"rating": {"count": "30", "average": "4.7"},
"kitchen_robot_name": "Bimby"
}
kitchen-robot resolves to "Bimby" for PT and "Thermomix" for UK. The display name is included in kitchen_robot_name.
Returns linked recipes in other languages. Useful for hreflang tags and locale switching.
GET /v1/recipes/bacalhau-a-bras/links
[
{
"slug": "shredded-cod-with-eggs",
"locale": "en-gb",
"title": "Shredded Cod with Eggs"
}
]
Top 20 recipes by session count for the current week.
[
{
"slug": "bacalhau-com-natas",
"title": "Bacalhau com Natas",
"image": "146",
"total_time": "50",
"difficulty": "easy",
"sessions": "219",
"week_start": "2026-03-30"
},
{
"slug": "polvo-a-lagareiro",
"title": "Polvo à Lagareiro",
"image": "21",
"total_time": "55",
"sessions": "181",
"week_start": "2026-03-30"
}
]
Returns recipes in a specific category. Category slugs are locale-specific.
GET /v1/recipes/by-category/peixe // PT: fish recipes
GET /v1/recipes/by-category/fish?locale=en-gb // UK: fish recipes
Returns recipes compatible with a specific cooking mode.
Modes: kitchen-robot, airfryer, mc
Returns average rating and count for all recipes.
POST /v1/recipes/rate
{"slug": "bacalhau-a-bras", "rating": 5}
One rating per IP per recipe (upsert).
350 ingredients with localized names, unit conversions, SEO data, and supermarket search terms.
Returns ingredient ID, localized name, slug, pack size, and pack unit.
[
{
"id": "167",
"name": "abacate",
"slug": "abacate",
"pack_size": "1",
"pack_unit": "un"
},
{
"id": "109",
"name": "abóbora hokkaido",
"slug": "abobora-hokkaido",
"pack_size": "1000",
"pack_unit": "g"
}
]
Returns ingredient details, unit conversions, and all recipes using this ingredient.
{
"id": "25",
"name": "bacalhau desfiado e demolhado",
"slug": "bacalhau-desfiado-e-demolhado",
"pack_size": "400",
"pack_unit": "g",
"canonical_name": "bacalhau desfiado e demolhado",
"conversions": [],
"recipes": [
{"slug": "bacalhau-a-bras", "title": "Bacalhau à Brás", "image": "147", "total_time": "35"},
{"slug": "bacalhau-com-natas", "title": "Bacalhau com Natas", "image": "146", "total_time": "50"}
]
}
Returns only ingredients that appear in at least one recipe. Ideal for "search by ingredient" features.
[
{"id": "1", "name": "azeite virgem extra", "slug": "azeite-virgem-extra"},
{"id": "14", "name": "ovos", "slug": "ovos"},
...
]
Returns conversion factors (e.g., 1 tablespoon of flour = 10g).
Returns how_to_choose, how_to_store, tip, and FAQs for each ingredient.
Real supermarket prices updated regularly. PT: Continente, Auchan, Pingo Doce, Aldi. UK: Tesco, Waitrose.
Returns current prices for all ingredients across all supermarkets in the locale.
[
{
"ingredient_id": "167",
"ingredient_name": "abacate",
"ingredient_slug": "abacate",
"supermarket": "continente",
"supermarket_name": "Continente",
"price": "2.05",
"product": "Abacate Biológico Continente Bio",
"packaging": "emb. 300 gr (2 un)",
"normalized_price": "1.03",
"date": "2026-03-15"
}
]
Filter by supermarket code.
PT codes: continente, auchan, pingodoce, aldi-pt
UK codes: tesco, sainsburys, asda, waitrose, aldi-uk, lidl-uk
Returns up to 100 historical price entries, ordered by date descending.
Returns locale-specific categories with slug, label, icon, color, and recipe count.
PT: carne, peixe, vegetariano, sobremesas, sopas, lanches, tradicional, rapidas
UK: meat, fish, vegetarian, vegan, desserts, soups, snacks, bbq, traditional, quick, pasta, breakfast, salads, oven, sauces
[
{
"id": "2",
"slug": "carne",
"label": "Carne",
"description": "Pratos de carne para todos os gostos",
"icon": "meat",
"color": "red",
"sort_order": "0",
"recipe_count": "116"
},
{
"id": "1",
"slug": "churrasco",
"label": "Churrasco",
"icon": "fire",
"color": "orange",
"sort_order": "0",
"recipe_count": "19"
}
]
Returns substitution rules with ratio, notes, and tags (vegan, lactose-free, etc.).
Returns all substitution options for a given ingredient, with keywords and tags.
GET /v1/substitutions/manteiga
{
"ingredient_slug": "manteiga",
"keywords": ["manteiga"],
"substitutions": [
{
"ingredient_name": "Manteiga",
"category": "lacteos",
"substitute_slug": "azeite",
"substitute_name": "Azeite",
"substitute_ingredient_id": "1",
"ratio": "3/4 da quantidade",
"ratio_value": "0.75",
"note": "Funciona em refogados e massas salgadas",
"tags": ["mais-saudavel", "sem-lactose", "vegano"]
},
{
"substitute_slug": "margarina-vegetal",
"substitute_name": "Margarina vegetal",
"ratio": "1:1",
"ratio_value": "1.00",
"note": "Verificar se é sem lactose",
"tags": ["mais-barato"]
}
]
}
Returns local stores (grocers, butchers, markets, etc.) with addresses and coordinates.
[
{
"id": "1",
"name": "Mercearia do Bairro",
"type": "mercearia",
"color": "#78716c",
"rank": "3",
"active": "1",
"locations": [
{
"city": "Lisboa",
"address": "Rua da Prata 42",
"phone": "+351 21 123 4567",
"lat": "38.7100000",
"lng": "-9.1370000"
}
]
}
]
GET /v1/stores/city/lisboa
GET /v1/stores/city/london?locale=en-gb
Given a city and ingredient IDs, returns stores ranked by ingredient coverage.
POST /v1/stores/recipe
{"city": "lisboa", "ingredient_ids": [1, 25, 48, 14]}
[
{
"id": "1",
"name": "Mercearia do Bairro",
"type": "mercearia",
"color": "#78716c",
"rank": "3",
"address": "Rua da Prata 42",
"phone": "+351 21 123 4567",
"covered_ingredients": "1,25,14",
"coverage": "3"
}
]
Active supermarket list for the authenticated locale, with brand colour and search URL — useful when rendering price comparison charts or "buy at" CTAs.
Returns the supermarkets that are active for the locale tied to your API key (override with ?locale=en-gb). PT returns Continente, Auchan, Pingo Doce, Aldi. UK returns Tesco, Sainsbury's, Asda, Aldi, Lidl, Waitrose.
[
{
"code": "continente",
"name": "Continente",
"color": "#c4354a",
"search_url": "https://www.continente.pt/pesquisa/?q="
},
{
"code": "auchan",
"name": "Auchan",
"color": "#b07040",
"search_url": "https://www.auchan.pt/pesquisa?q="
}
]
Creates a new list and returns UUID + short code for sharing.
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"short_code": "f8e2a1b3"
}
Returns list with recipes, servings, and pantry items. Accepts UUID or short code.
{
"id": "1",
"locale": "pt",
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"short_code": "f8e2a1b3",
"recipes": [
{"slug": "bacalhau-a-bras", "title": "Bacalhau à Brás", "image": "147", "servings": "4"}
],
"pantry": [
{"ingredient_id": "48", "name": "água", "qty": "1.00", "unit": "L"}
]
}
POST /v1/lists/a1b2c3...
{
"recipes": [{"slug": "bacalhau-a-bras", "servings": 4}],
"pantry": [{"ingredient_id": 48, "qty": 1, "unit": "L"}]
}
Returns all active locales with currency, country, domain, and kitchen robot brand name.
[
{
"code": "pt",
"name": "Português",
"currency": "EUR",
"country": "Portugal",
"domain": "bourmet.pt",
"kitchen_robot_name": "Bimby",
"active": "1"
},
{
"code": "en-gb",
"name": "English (UK)",
"currency": "GBP",
"country": "United Kingdom",
"domain": "bourmet.co.uk",
"kitchen_robot_name": "Thermomix",
"active": "1"
}
]
Returns locale info plus supermarkets, recipe count, ingredient count, and category count.
{
"code": "pt",
"name": "Português",
"currency": "EUR",
"country": "Portugal",
"domain": "bourmet.pt",
"kitchen_robot_name": "Bimby",
"supermarkets": [
{"code": "continente", "name": "Continente", "active": "1"},
{"code": "auchan", "name": "Auchan", "active": "1"},
{"code": "pingodoce", "name": "Pingo Doce", "active": "1"},
{"code": "aldi-pt", "name": "Aldi", "active": "1"}
],
"recipe_count": 460,
"ingredient_count": 294,
"category_count": 8
}
Portuguese wine recommendations matched to recipes by type (tinto, branco, verde, rosé, moscatel, porto, espumante).
Returns distinct wine types with the number of wines available for each.
[
{"wine_type": "espumante", "count": "2"},
{"wine_type": "moscatel", "count": "2"},
{"wine_type": "porto", "count": "1"},
{"wine_type": "rose", "count": "2"},
{"wine_type": "vinho_branco", "count": "3"},
{"wine_type": "vinho_tinto", "count": "4"},
{"wine_type": "vinho_verde", "count": "2"}
]
Returns wines available for the given type. Use with the pairing_types field from recipe responses.
GET /v1/wine/pairing/vinho_branco
[
{
"wine_name": "Quinta da Aveleda Vinho Verde",
"wine_type": "vinho_branco",
"description": "Branco fresco e mineral do Minho...",
"url": "https://example.com/wine",
"brand_name": "Aveleda"
}
]
pairing_types field tells you which wine type to query. E.g., {"category":"wine","type_key":"vinho_branco"} means call /wine/pairing/vinho_branco.
Portuguese craft beer recommendations matched to recipes by style (IPA, APA, pilsner, lager, stout, porter, weiss, sour, belgian ale, trigo).
Returns distinct beer styles with the number of beers available for each.
[
{"beer_type": "apa", "count": "2"},
{"beer_type": "belgian_ale", "count": "2"},
{"beer_type": "ipa", "count": "2"},
{"beer_type": "lager", "count": "2"},
{"beer_type": "pilsner", "count": "2"},
{"beer_type": "porter", "count": "2"},
{"beer_type": "sour", "count": "2"},
{"beer_type": "stout", "count": "2"},
{"beer_type": "trigo", "count": "2"},
{"beer_type": "weiss", "count": "2"}
]
Returns craft beers available for the given style.
GET /v1/beer/pairing/ipa
[
{
"beer_name": "Dois Corvos Matiné IPA",
"beer_type": "ipa",
"description": "IPA fresca e cítrica, equilibrada entre lúpulos e maltes...",
"url": null,
"brand_name": "Dois Corvos"
},
{
"beer_name": "Letra G IPA",
"beer_type": "ipa",
"description": "IPA intensa e aromática, com amargor marcado...",
"url": null,
"brand_name": "Letra"
}
]
Profiles of chefs with whom Bourmet has signed content partnerships. Each chef brings exclusive recipes, sponsor approvals, social links and a featured slot for the consumer apps. Currently active: Hélio Loureiro (PT).
Pro or above. Free / Basic plans get a 403.
Lightweight list of active chefs (id, slug, name, title, photo, tagline, accent colour). Use this as the index for picker UIs.
[
{
"id": 1,
"slug": "helio-loureiro",
"name": "Hélio Loureiro",
"title": "Chef Executivo",
"photo": "chefs/helio-loureiro.jpg",
"tagline": "Cozinha portuguesa contemporânea",
"signature_color": "#0F4C5C",
"accent_color": "#E36414"
}
]
Returns the chef currently flagged as is_featured=1, with stats (years cooking, awards, books), achievements, social links and all published recipes. Designed for "Chef em destaque" homepage blocks.
Full profile: bio (short + long), title, hero image, signature/accent colours, contact email, plus stats, achievements, books, and socials.
GET /v1/chefs/helio-loureiro
Recipes where chef_id matches the slug. Same shape as the regular recipe list (id, slug, title, image, total_time, difficulty, categories).
GET /v1/chefs/helio-loureiro/recipes
List of sponsor brands the chef has personally approved (or flagged as official partner). Each entry includes brand name, image, partner flag and whether the approval is active.
GET /v1/chefs/helio-loureiro/sponsors
Generate budget-optimized weekly meal plans, swap individual recipes without rebuilding the whole plan, and share results via short token. Built on top of the recipes + ingredients + prices catalog.
Pro or above. Free / Basic plans get a 403.
Returns an optimized plan that fits the constraints (budget, meals, servings, difficulty, cooking modes). Output includes the picked recipes, total cost, ingredient roll-up and a per-supermarket coverage estimate.
{
"budget": 50.00,
"meals": 7,
"servings": 2,
"meal_types": ["almoco", "jantar"],
"exclude_categories": ["sobremesa"],
"modes": ["airfryer"],
"max_difficulty": "medium",
"fixed_store": "continente"
}
| Field | Default | Notes |
|---|---|---|
budget | required | Total budget for the plan (locale currency) |
meals | 7 | 1–21 |
servings | 2 | 1–10 |
meal_types | ["almoco","jantar"] | Filter by meal slot |
modes | any | kitchen-robot, airfryer, mc |
max_difficulty | any | easy, medium, hard |
fixed_store | cheapest | Force a single supermarket for cost calc |
Replace one slot in an existing plan with a different recipe that respects the same constraints. Useful for "I don't feel like fish today" UX.
{
"plan": { ... existing plan object ... },
"swap_index": 2,
"exclude_recipe_ids": [142, 87]
}
Saves a generated plan to the database and returns a short share token plus a long UUID. The token is the path component used by the GET endpoint below.
Public-by-token retrieval — anyone with the token can see the plan, no API key restriction beyond the per-key rate limit. Designed to be shareable.
Long-form lifestyle and how-to content (e.g. "Tipos de sal e quando usar"). Every guide is locale-scoped, has a hero image, sections, FAQs and related recipes.
Returns guide cards (id, slug, category, title, subtitle, intro, hero_image, reading_time_min, published_at). Default 20 per page, max 100.
GET /v1/guides
GET /v1/guides?category=ingredientes&sort=published_at&order=desc
GET /v1/guides?search=sal&per_page=10
| Param | Example | Description |
|---|---|---|
category | ingredientes | Filter by category slug |
search | sal | Search title/subtitle/intro |
sort | published_at | published_at, title, category, created_at |
order | desc | asc, desc |
Convenience endpoint for category landing pages. Equivalent to /guides?category=... but with a cleaner URL.
Returns published guides whose related_recipes contain the given recipe slug. Used for the "Saiba mais" section on recipe pages — the inverse of the recipe list embedded in each guide.
GET /v1/guides/by-recipe/bacalhau-assado-com-batatas
Returns the full guide payload: hero, intro, ordered sections (heading + body + optional images), FAQs and related recipes (linked by ingredient overlap).
GET /v1/guides/ingredientes/sal-tipos-quando-usar
Recipe images are named by PT recipe ID and served as static files.
| Type | Path | Format |
|---|---|---|
| Original | /assets/recipes/{id}.jpg | Full quality JPG |
| Mobile | /assets/mobile/{id}.webp | 768px WebP (~50-80KB) |
| Audio | /assets/audio/{id}/step-{n}.mp3 | TTS narration (PT only) |
| Video | /assets/videos/{id}.mp4 | Recipe video |
image field in recipe responses contains the ID. Build the full URL as: https://bourmet.dev/assets/recipes/{image}.jpg
UK recipes share images with their PT counterparts. The image field for linked UK recipes contains the PT recipe ID.
Assets are served with Cache-Control: public, max-age=31536000, immutable and CORS headers.
Pick the tier that matches your usage. Endpoints marked Premium in the reference above require Pro or above.
To request an API key, contact miguel.teixeira@bourmet.pt. Free keys are issued automatically.
| Code | Meaning | Response |
|---|---|---|
| 401 | Missing or invalid API key | {"error": "Missing API key"} |
| 403 | Key expired or endpoint restricted | {"error": "API key expired"} |
| 404 | Resource not found | {"error": "Recipe not found"} |
| 405 | Method not allowed | {"error": "Method not allowed"} |
| 429 | Rate limit exceeded | {"error": "Daily rate limit exceeded", "limit": 100, "used": 101} |
All error responses include an error field with a human-readable message.