JavaScript toolkit to inspect the HelloFresh frontend bootstrap payload and replay a few internal gw/* endpoints using the SSR Bearer token exposed in __NEXT_DATA__.
- fetches a HelloFresh page (
/plansby default) - extracts
serverAuthandinternalRewritesfrom__NEXT_DATA__ - builds URLs for:
GET /gw/menus-service/weeksGET /gw/menus-service/menus
- replays those requests with the SSR
Bearertoken
npm installnpm run extract -- --endpoint bootstrapnpm run extract -- --endpoint weeks --country LU --locale en-GB --brand hellofreshnpm run extract -- --endpoint menus --country LU --weeks 2026-W18 --locale en-GB --take 1JSON payload:
npm run bearerRaw token only:
npm run bearer -- --rawVia the generic CLI:
npm run extract -- --endpoint bearer
npm run extract -- --endpoint bearer -- --rawnpm run extract -- --endpoint profilesnpm run extract -- --endpoint shapesnpm run extract -- --endpoint http
npm run generate:httpGenerated file:
HF_api.http
Observed behavior:
- no query params →
200, but{"weeks":[]} country=LU→200, returns populated weekslocale=en-GBalone →200, but still emptybrand=hellofreshalone →200, but still emptycountry=LU&locale=en-GB→ populatedcountry=LU&brand=hellofresh→ populatedcountry=DE&locale=de-DE→ populated in live testcountryis the practical minimum useful param
Parameter profile:
| Param | Effect | Notes |
|---|---|---|
country |
required for non-empty data | Without it, endpoint still returns 200 but weeks is empty |
locale |
optional | Accepted; does not make the response useful by itself |
brand |
optional | Accepted; does not make the response useful by itself |
Recommended form:
/gw/menus-service/weeks?country=LU&locale=en-GB&brand=hellofresh
Observed behavior:
- no
country→400withcountry cannot be empty country=LUalone →200, huge mixed result set across weeks/products (total=7189in the live probe)weeks=2026-W18narrows results to one week (total=15in the live probe)locale=en-GBis acceptedbrand=hellofreshis acceptedtakeworks for pagination / limitingskipworks for pagination offsetproduct=classic-menuworks and narrows to the matching menu familyproducts=classic-menualso works in the same way in live testsproductSku=LU-CB-3-2-0works and narrows to matching itemsexclude=recipes.category,recipes.nutrition,recipes.stepsis accepted, but in current live checks those nested recipe fields still appeared in the payloadproduct=familyandproducts=familyreturned empty result sets in the tested week
Parameter profile:
| Param | Effect | Notes |
|---|---|---|
country |
required | Without it, request fails with 400 |
weeks |
strong filter | Best lever to shrink the dataset to one week |
locale |
optional | Accepted; no visible structural change in tested responses |
brand |
optional | Accepted; no visible structural change in tested responses |
take |
pagination limit | Changes count, keeps total |
skip |
pagination offset | Moves the item window |
product |
filter | Works with values like classic-menu |
products |
filter | Behaved like product in tested requests |
productSku |
filter | Restricts to items carrying the SKU |
exclude |
accepted but no visible change | Nested recipe fields still present in live payload |
Recommended form:
/gw/menus-service/menus?country=LU&weeks=2026-W18&locale=en-GB&take=1
More selective example:
/gw/menus-service/menus?country=LU&weeks=2026-W18&locale=en-GB&product=classic-menu&productSku=LU-CB-3-2-0&take=1
Top-level shape:
weeks
Observed sample:
- first week:
2026-W17
Top-level shape:
itemstakeskipcounttotal
Observed items[0] keys:
idcountryproductproductSKUsweekheadlineisActiveisCompleteisReadOnlyserializedPreferencespreferencescoursesmodularitysurveyTitlesurveyQuestionsurveyBodysurveyOptInaverageRatingratedlinkcreatedAtupdatedAtclonedFrommealSwapCombinationsmealSwapCombinationsText
Observed courses[0] keys:
indexrecipeselectionLimitisSoldOuthideOnSoldOutsoldOutThresholdchargeSettingisHiddensectionspresetsshoppingSegmentsshoppableProductId
Observed recipe keys:
activeaverageRatingcategorycountrycuisinesdifficultyfavoritesCountheadlineidimageLinkimagePathingredientsisPublishedlabelnamenutritionprepTimeratingsCountslugstepstagstotalTimeuuidwebsiteUrlyields
Observed nested shapes:
category:{ id, name, slug, type }cuisines[]:{ id, name, slug, type }tags[]:{ id, name, slug, type, displayLabel, colorHandle, preferences }nutrition[]:{ type, name, amount, unit }
Live-probe caveat:
- in the tested payload,
ingredients,steps, andyieldswere present as keys but empty arrays nutritionwas populated
A ready-to-use request collection is generated at:
HF_api.http
It contains:
- bootstrap page request
- recommended
weeksrequest - recommended
menusrequest - filtered
menusrequest byproductandproductSku - pagination example
excludeexample
It now also centralizes shared variables so the file is easier to maintain:
@baseUrl@country@locale@brand@week@token@jsonAccept@plansPage@weeksPath@weeksCountryOnlyPath@menusBase
That means most requests now reuse {{menusBase}} or {{weeksPath}} instead of repeating the full URL and repeated params.
- this is not an official public API
- the SSR token may be ephemeral
- endpoint behavior may change at any time
- do not log or publish raw tokens
npm test