Feeding 4 for £50 at ASDA — the script that settled the bet
This script tests whether feeding four people for a week on £50 is achievable at ASDA. It uses Pepesto's /suggest endpoint to find budget-friendly dinners, then /products to price each ingredient list at asda.com — tallying the total against the target.
Run this yourself
$ PEPESTO_API_KEY=your_key node asda-budget-week-50-pounds.jsFull script: asda-budget-week-50-pounds.js. You'll need an API key to run it — get one here.
Getting started
One API key, one environment variable, same as always.
async function requestApiKey(email) {
console.log(`\nRequesting API key for: ${email}`);
const response = await fetch(`${BASE_URL}/link`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
if (!response.ok) {
const text = await response.text();
throw new Error(`POST /api/link failed (${response.status}): ${text}`);
}
const data = await response.json();
console.log('\n✓ Done, data contains now your api key.');
console.log('\nOnce you have your key, run:');
console.log(' export PEPESTO_API_KEY=pep_sk_your_key_here\n');
return data;
}The first call — finding budget recipes with /suggest
The /suggest endpoint searches the Pepesto recipe database without needing a URL. Pass a free-text query and get back structured recipes — complete with ingredients, steps, and a kg_token ready for the product lookup step.
// /suggest returns 3 recipes per call, so several queries are needed to fill a
// week. The same dish can come back from two queries — and even twice within a
// single call — with a different kg_token each time, so duplicates have to be
// dropped by title. Each query pulls on a different main ingredient.
async function suggestRecipes(limit = 5) {
const queries = [
'cheap family dinner with chicken, under 10 pounds',
'cheap family dinner with beef mince, budget friendly',
'cheap vegetarian family dinner, budget friendly',
];
const responses = await Promise.all(queries.map(async (query) => {
const response = await fetch(`${API_BASE}/suggest`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`,
},
body: JSON.stringify({ query }),
});
const text = await response.text();
if (!response.ok) {
throw new Error(`/suggest failed: ${response.status} ${text}`);
}
return JSON.parse(text);
}));
const seen = new Set();
const recipes = [];
for (const r of responses.flatMap(d => d.recipes)) {
const key = r.title.trim().toLowerCase();
if (seen.has(key)) continue;
seen.add(key);
recipes.push({
title: r.title,
kg_token: r.kg_token,
nutrition: r.nutrition,
ingredients: r.ingredients,
});
}
if (recipes.length < limit) {
console.log(`Only ${recipes.length} distinct dinners came back — planning for those.`);
}
return recipes.slice(0, limit);
}Each call comes back with three fully structured recipes. Here's one of them:
{
"title": "Baked Tortillas with Minced Meat, Vegetables, and Cheese",
"ingredients": [
"130g canned corn", "1 red pepper (~110g)", "1 yellow bell pepper (~150g)",
"400g canned tomatoes", "1 onion (~150g)", "1 bunch parsley",
"200g cheddar cheese", "700g ground beef", "300g tortillas",
"sunflower oil", "salt", "black pepper", "chilli powder",
"smoked paprika", "soy sauce"
],
"nutrition": { "calories": 4287, "protein_grams": 238, "fat_grams": 260 },
"kg_token": "EjoKOEJha2VkIFRvcnRpbGxhcyB3aXRoIE1pbmNlZCBNZWF0..."
}Then I hit /products for each recipe at asda.com, picking the cheapest product per ingredient line.
async function fetchAsdaProducts(kgToken) {
const response = await fetch(`${API_BASE}/products`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`,
},
body: JSON.stringify({
recipe_kg_tokens: [kgToken],
supermarket_domain: 'asda.com',
}),
});
if (!response.ok) {
const text = await response.text();
throw new Error(`/products failed: ${response.status} ${text}`);
}
const data = await response.json();
return data.items;
}Then one helper takes the cheapest matching product for every ingredient line and adds them up:
function cheapestCost(items) {
return items.reduce((total, item) => {
if (!item.products || item.products.length === 0) return total;
// Sort by price ascending to find the cheapest match
const prices = item.products.map(p => p.product.price.price).sort((a, b) => a - b);
return total + prices[0];
}, 0);
}main() runs those two across all five recipes in parallel and tallies the week.
What the data showed
The script returned £43.17 for five dinners at ASDA, using the cheapest available product per ingredient. Note that some ingredients are priced for pack quantities you won't fully use in a single recipe — the smoked paprika being a typical example.
A few observations from the data. ASDA's own-brand ranges are competitive: ASDA Finely Sliced Bavarian Style Ham 120g came in at £2.24 including a live promotion. Canned goods were uniformly low: Island Sun Sweetened Condensed Milk 397g at £1.47, Tropical Sun Cornmeal Fine Polenta 500g at £1.00. Fresh protein was the main budget driver — chicken thighs and beef mince pushed individual meal costs toward £12–14.
Next steps
From a list of priced items, the natural next step is a cart. Build a skus array from the matched products (each with session_token and num_units_to_buy), call /api/session with the supermarket_domain and skus, then pass the returned session_id to /api/checkout to get the basket URL:
{
"session_id": "ses_4rQnBwKpLxZm7vTe"
}Pass the session_id to /checkout to get a pre-filled ASDA basket link. Since ASDA doesn't have a native guest-shareable cart URL, the Pepesto session acts as the share mechanism — you send the link to anyone in the household and they can open it and order.
The result
The main implementation work is formatting the output table — a per-meal line, a grand total, and an over/under budget verdict. The API calls themselves are straightforward.
Running this weekly over a month, results vary by about £4–8 depending on active promotions and which specific recipes the query returns. The £50 budget is achievable most weeks — the main risk is beef mince pricing.
What else you could do?
Three natural extensions. Filter for items currently on promotion — ASDA runs significant rollback pricing that the promo: true flag surfaces directly. Add a "prefer own-brand" sort that deprioritises branded products when a cheaper ASDA own-brand match exists on the same ingredient line. Run the same five meals against Tesco and Morrisons using the same kg_tokens — no re-parsing needed — to find the cheapest store for your specific recipe set.