Danish Meal Prep β 5 Work Lunches, One Nemlig Order, One Script
This automates Sunday meal prep planning using Pepesto's /suggest endpoint to find 5 suitable lunch recipes, /products to price each at Nemlig, and /session to create a single Danish delivery order β from recipe suggestions to checkout link in one script.
Run this yourself
$ PEPESTO_API_KEY=your_key node nemlig-dk-meal-prep-five-lunches.jsFull script: nemlig-dk-meal-prep-five-lunches.js. You'll need an API key to run it β get one here.
Getting started
export PEPESTO_API_KEY=your_key_here
node nemlig-dk-meal-prep-five-lunches.jsThe first call β suggesting five lunches
The /suggest endpoint accepts a free-text query. Put meal type and prep constraints directly in the query string β the API uses it to select recipes from the database that hold well in containers over several days.
// /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 style of lunch.
async function suggestLunches(target = 5) {
console.log('Searching for healthy lunch meal prep recipes...');
const queries = [
'healthy make ahead lunch with chicken',
'healthy meal prep lunch with grains or salad',
'healthy vegetarian meal prep lunch',
];
const responses = await Promise.all(queries.map(async (query) => {
const response = await fetch(`${BASE_URL}/suggest`, {
method: 'POST',
headers,
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 lunches = [];
for (const recipe of responses.flatMap(d => d.recipes)) {
const key = recipe.title.trim().toLowerCase();
if (seen.has(key)) continue;
seen.add(key);
lunches.push(recipe);
}
if (lunches.length < target) {
console.log(`Only ${lunches.length} distinct lunches came back β planning for those.`);
}
return lunches.slice(0, target);
}{
"recipes": [
{
"title": "Greek-style baked fish with tomatoes and cheese",
"ingredients": [
"2 tomatoes (~350g)",
"3 garlic cloves",
"150g parmesan cheese",
"200ml sour cream",
"700g fish fillets",
"salt",
"black pepper"
],
"nutrition": {
"calories": 1883,
"carbohydrates_grams": 25,
"protein_grams": 190,
"fat_grams": 91
},
"kg_token": "EjEKL0dyZWVrLXN0eWxlIGJh..."
},
{
"title": "Sweet Potato and Beef Cypriot Meatballs",
"ingredients": [
"500g sweet potatoes",
"1 onion (~150g)",
"50g parsley",
"2 eggs",
"500g ground beef",
"100g breadcrumbs",
"cinnamon powder",
"cumin seeds",
"salt"
],
"nutrition": {
"calories": 2260,
"carbohydrates_grams": 189,
"protein_grams": 141,
"fat_grams": 108
},
"kg_token": "EikKJ1N3ZWV0IFBvdGF0by..."
}
]
}Matching to Nemlig products
All five kg_token values go into a single /products call. The API returns Nemlig products with prices in ΓΈre (1/100 DKK). Products with promo: true are currently on tilbud.
async function getNemligProducts(kgTokens) {
console.log(`\nFetching Nemlig products for ${kgTokens.length} recipes...`);
const res = await fetch(`${BASE_URL}/products`, {
method: 'POST',
headers,
body: JSON.stringify({
recipe_kg_tokens: kgTokens,
supermarket_domain: 'nemlig.com',
}),
});
if (!res.ok) throw new Error(`/products failed: ${res.status}`);
return res.json();
}{
"items": [
{
"item_name": "Ham",
"products": [
{
"product": {
"product_name": "Tulip smoked ham",
"product_name_da": "Γ
lerΓΈget skinke, Tulip",
"quantity": { "grams": 90 },
"price": {
"price": 1995,
"promotion": { "promo": true }
}
},
"session_token": "eyJwcm9kdWN0...",
"num_units_to_buy": 1
}
]
},
{
"item_name": "Apricots",
"products": [
{
"product": {
"product_name": "Apricots (Nordthy)",
"product_name_da": "Abrikoser (Nordthy)",
"quantity": { "grams": 100 },
"price": {
"price": 1200,
"promotion": { "promo": true }
}
},
"session_token": "eyJwcm9kdWN0...",
"num_units_to_buy": 1
}
]
}
]
}Creating the Nemlig session
// One basket line per matched ingredient. /products orders each item's matches
// cheapest first, so the first entry is the one to buy.
function buildSkus(items) {
return items
.filter(item => item.products?.length > 0)
.map(item => ({
session_token: item.products[0].session_token,
num_units_to_buy: item.products[0].num_units_to_buy || 1,
}));
}That list of basket lines is what /session takes:
async function createSession(skus) {
console.log('\nCreating Nemlig checkout session...');
const res = await fetch(`${BASE_URL}/session`, {
method: 'POST',
headers,
body: JSON.stringify({ supermarket_domain: 'nemlig.com', skus }),
});
if (!res.ok) throw new Error(`/session failed: ${res.status}`);
return res.json();
}{
"session_id": "ses_9mWqX3yPkNb2"
}What the data showed
Five meal prep recipes produced 32 unique ingredient lines. After de-duplication across the recipes (onions, garlic, and salt each appeared in multiple meals), the Nemlig basket had 28 items. Nemlig prices in DKK ΓΈre are large numbers β 19.95 kr looks like 1995 β so the formatting step in the script matters if you want readable output.
Several products came back with promo: true. Tulip smoked ham (Γ
lerΓΈget skinke) was on tilbud at 19.95 kr for 90g, and Apricots (Nordthy) were 12.00 kr for 100g β both good value for protein-rich lunch prep. The Apricot marmalade (Lie Gourmet) was also on promo, though at 59.95 kr for 370g it's more of a treat item.
Ingredient de-duplication across recipes works automatically. Three recipes called for garlic; the response had one "Garlic" line item with an appropriate unit count.
The result
Script runs in about 4 seconds. Pass the session_id to /checkout to get a Nemlig order link with 28 items pre-filled. Open it, review the basket, and choose a delivery slot.
What else you could do?
Add a calorie budget check β if the five lunches total more than 2,500 kcal per day across five portions, swap the highest-calorie recipe for a lighter one. Check Nemlig's delivery slot availability and auto-select the earliest Sunday morning slot. Save the recipe selections week-over-week and add a shuffle that avoids repeating the same lunch two weeks in a row.