Developer reference (The Lunch Box only). This page documents the recipe data integration that powers the site’s recipe search and detail pages. It is maintained by development.
The Lunch Box’s recipes are fed by the HealthePro recipe API/JSON. The recipe search page, recipe detail pages, and the downloadable nutrition/cost reports are all built from HealthePro data.
On the front end this is rendered by HealthePro-specific components:
BlockRecipesViewHealthePro — the recipe search/listing moduleCardRecipeHealthePro — a recipe card in listingsPageRecipeHealthePro — a recipe detail pageEverything about a recipe — copy, image, category, attributes, allergens, ingredients, nutrition — is maintained in HealthePro; nothing is edited in Craft. See the “Recipes (HealthePro)” section on the “Site Specific” page for the editor workflow.
There is no middleware server or intermediate database. The site talks to HealthePro’s external REST API directly over HTTPS with a bearer token (the HEALTHE_PRO_API / HEALTHE_PRO_AUTH environment variables). The token is used server-side only and is never sent to the browser.
Two mechanisms are in play:
svr-scripts/fortrabbit/healthe-pro/, run from the Fortrabbit cron queue — classic Fortrabbit’s smallest interval is one hour) walks every page of /recipes, fetches each recipe’s detail record, and publishes an optimized JSON feed at web/api/healthe-pro/healthe-pro-recipes-active.json — not a mirror of HealthePro, but each active recipe reduced to the handful of fields the search page needs to list, filter and sort (see “Search-page feed record” below) (plus -inactive and -error files, and a locally resized 500 px card thumbnail per active recipe). The feed carries a content-hash version token so browsers pick up a rebuilt feed immediately. The previous feed is kept as a gzip backup for 7 days. The recipe search page downloads the feed once and does all searching, filtering, sorting and paging in the browser.Only recipes flagged active are published to the search page; inactive recipes’ detail URLs redirect (302) to the recipe index. Legacy OneSource recipe codes (e.g. DR008) 301-redirect to the matching HealthePro id via config/onesource_mappings.php.
Base URL: https://menuplan.healthepro.com/api/v1/external/organizations/{orgId} (the full base, including the organization id, is the HEALTHE_PRO_API env var). Auth: Authorization: <bearer token> header. HealthePro rate limit: 300 requests/minute.
| Endpoint | Used by |
|---|---|
GET /recipes?order_by=name&page=N | feed build (paginated; meta.last_page gives the page count); status monitor (catalog check) |
GET /recipes/{id} | feed build (per-recipe detail); recipe detail page |
GET /recipes/{id}?scale=N | “Scale Recipe”; PDF/XLSX export; status monitor (scaling check) |
GET /recipe_categories | Category filter on the search page (restricted and ordered to a fixed list of 14 food categories) |
Fields consumed from /recipes/{id}: id, name, active, image (signed CloudFront URL, expires in 1 hour — hence the locally cached thumbnails), category, category_id, attributes[].name, allergens[].name, food_process.name (HACCP), serving_size, serving_measure, yield, ingredients[] (id, name, quantity, quantity2), preparation_instructions (HTML), meal_components.*, nutrients.*.
Search-page feed record (reduced object per recipe): id, name, category, category_id, image, thumb (site-hosted thumbnail), allergens and attributes as comma-separated strings, active, status. The feed’s top-level filters object holds the de-duplicated allergen and attribute names that populate the two filter dropdowns.
Site-side endpoints (Craft templates / PHP): /action-healthepro-categories/, /action-recipe-ingredients-healthepro/?id=&servings=, /api/recipe-export.php (POST), /recipe-status/.
History: this integration replaced the OneSource/Horizon system (2025-11), which ran through a CAF-owned middleware server on Linode at
recipe-api.thelunchbox.org. That server is decommissioned; the hostname now simply aliases to the status page below.
There’s a simple, public status page that reports whether the HealthePro recipe service is up or down. It’s meant for an uptime monitor (Uptime Robot, or similar) so you get an alert the moment recipes stop working. It replaces the old recipe-api.thelunchbox.org/Status service.
Status URL: https://recipe-api.thelunchbox.org/ — the dedicated address for monitoring the recipe service. It points at the site’s own status page (https://www.thelunchbox.org/recipe-status/); either address works and returns the same thing.
Open it in a browser and you’ll get a small JSON response. The two things to look at are the HTTP status code the page returns and the "status" value in the response:
"up" (returns HTTP 200) — HealthePro is reachable and serving recipes. All good."down" (returns HTTP 503) — HealthePro is unreachable, timed out, or rejected the login. The recipe search and recipe detail pages will be broken until it’s back."degraded" (returns HTTP 200) — recipes still load, but ingredient scaling (the part that recalculates ingredient amounts for a serving count) is failing.Under the hood it checks two things separately, shown in the response as catalog (can we load the recipe list?) and scaling (can we scale a real recipe?).
https://recipe-api.thelunchbox.org/. The default behaviour — treat 200 as up, anything else as down — is all you need; a 503 will trigger the alert.https://recipe-api.thelunchbox.org/?require=scaling. This one goes down specifically when ingredient scaling breaks, even if the recipe list is still working — the equivalent of the old IngredientsStatus check. Skip it if you only care about total outages.If you’re updating an existing check, just swap its old recipe-api.thelunchbox.org/Status URL for https://recipe-api.thelunchbox.org/.
Add ?debug=error to the URL — for example https://recipe-api.thelunchbox.org/?debug=error — and the page adds a short plain-English note about which check failed. No login is needed to view the status page, and it never shows any HealthePro password or key.