πŸ‡©πŸ‡° Nemlig Example solution JavaScript /suggest + /products

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.js

Full script: nemlig-dk-meal-prep-five-lunches.js. You'll need an API key to run it β€” get one here.

Getting started

bash
export PEPESTO_API_KEY=your_key_here
node nemlig-dk-meal-prep-five-lunches.js

The 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.

js
// /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);
}
json β€” /suggest response (excerpt)
{
  "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.

js
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();
}
json β€” /products response (excerpt)
{
  "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

js
// 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:

js
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();
}
json β€” /session response
{
  "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.

Links

Ready to build?

Start meal-prepping with Nemlig

Generate 5 work lunches and build a complete Nemlig order in one script β€” /suggest, /products, /session.

27supermarkets 13countries 1schema Instant access