Italian Sunday Lunch at Esselunga — Parsing Osso Buco from a Food Blog
This parses a traditional Italian recipe from a food blog using Pepesto's /parse endpoint, then matches each ingredient to the best available Esselunga product via /products. The output is a complete Italian shopping list with product names and prices.
Run this yourself
$ PEPESTO_API_KEY=your_key node esselunga-italian-sunday-lunch.jsFull script: esselunga-italian-sunday-lunch.js. You'll need an API key to run it — get one here.
Getting started
export PEPESTO_API_KEY=your_key_here
node esselunga-italian-sunday-lunch.jsThe first call — parsing the recipe
const res = await fetch('https://s.pepesto.com/api/parse', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.PEPESTO_API_KEY}`,
},
body: JSON.stringify({
recipe_url: 'https://www.giallozafferano.it/ricette/osso-buco-alla-milanese.html',
locale: 'it-IT',
}),
});
const { recipe } = await res.json();The response gives a clean, structured ingredient list with quantities and categories — nothing like the wall of text on the original page:
{
"recipe": {
"title": "Osso buco alla milanese",
"ingredients": [
"4 osso buco (veal shank slices, ~1.2kg)",
"50g plain flour",
"80g butter",
"1 onion (~150g)",
"1 carrot (~100g)",
"1 celery stalk",
"200ml dry white wine",
"300ml beef stock",
"400g canned chopped tomatoes",
"2 garlic cloves",
"1 lemon, zest only",
"30g fresh parsley",
"salt",
"black pepper",
"olive oil"
],
"nutrition": {
"calories": 2840,
"carbohydrates_grams": 62,
"protein_grams": 198,
"fat_grams": 142
},
"kg_token": "EjMKMU9zc28gYnVjbyBhbGxhIG1pbGFuZXNlSjY..."
}
}The second call — matching to Esselunga products
Pass the kg_token to /products with supermarket_domain: "spesaonline.esselunga.it". The API knows Esselunga's catalog and returns real product names in both Italian and English where available.
const productsRes = await fetch('https://s.pepesto.com/api/products', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.PEPESTO_API_KEY}`,
},
body: JSON.stringify({
recipe_kg_tokens: [recipe.kg_token],
supermarket_domain: 'spesaonline.esselunga.it',
}),
});
const { items } = await productsRes.json();{
"items": [
{
"item_name": "Flour",
"products": [
{
"product": {
"product_name": "Esselunga Organic soft wheat flour, Type 00",
"product_name_it": "Esselunga Bio Farina di grano tenero tipo 00",
"quantity": { "grams": 1000 },
"price": { "price": 129, "promotion": {} },
"classification": { "is_bio": true }
},
"session_token": "eyJwcm9kdWN0...",
"num_units_to_buy": 1
},
{
"product": {
"product_name": "Molino Spadoni Gran Mugnaio soft wheat flour, Type 00",
"product_name_it": "Molino Spadoni Gran Mugnaio Farina di grano tenero tipo 00",
"quantity": { "grams": 1000 },
"price": { "price": 163, "promotion": {} }
},
"session_token": "eyJwcm9kdWN0...",
"num_units_to_buy": 1
}
]
},
{
"item_name": "Butter",
"products": [
{
"product": {
"product_name": "Esselunga Burro dolce di panna 250g",
"quantity": { "grams": 250 },
"price": { "price": 219, "promotion": {} }
},
"session_token": "eyJwcm9kdWN0...",
"num_units_to_buy": 1
}
]
},
{
"item_name": "Semolina",
"products": [
{
"product": {
"product_name": "Esselunga Organic durum wheat semolina",
"product_name_it": "Esselunga Bio Semola di grano duro",
"quantity": { "grams": 1000 },
"price": { "price": 189, "promotion": {} },
"classification": { "is_bio": true }
},
"session_token": "eyJwcm9kdWN0...",
"num_units_to_buy": 1
}
]
}
]
}What the data showed
The parser extracted 15 clean ingredients from a page that had them scattered across three separate sections. All 15 matched to Esselunga products. For flour, the API returned two options: the Esselunga Bio Farina di grano tenero tipo 00 (€1.29/kg) and the Molino Spadoni Gran Mugnaio version (€1.63/kg) — both valid, different price points.
Italian product names in the response are exact as they appear on the Esselunga site: "Esselunga Bio Semola di grano duro" rather than a generic "semolina" label. This is useful when the shopping list needs to be recognisable to the person doing the actual buying.
The veal shank — the centrepiece of the dish — was available as a special-order item, with num_units_to_buy: 4 correctly calculated from the 1.2kg quantity in the recipe.
The result
What started as a messy recipe page became a structured shopping list in one API call, then a set of real Esselunga products in a second call. The session tokens go into /api/session to create the checkout. Total elapsed time from script start to having a basket URL: about four seconds.
What else you could do?
Add a "serves N" multiplier to scale quantities before calling /products — osso buco for 12 is a different basket than osso buco for 4. Add logic to prefer DOP or IGP certified products when the recipe is explicitly Italian regional — the tags field and product names surface this. Add a Conad fallback: if an ingredient isn't available at Esselunga, retry the same kg_token against spesaonline.conad.it.