gotchatypescriptMajor
USDA FoodData Central: homemade dishes need the FNDDS dataset, POST requests, and a dedicated Survey query
Viewed 0 times
FNDDSSurveydataTypefoodMeasuresgramWeighthomemade foodmixed dishes400 bad request parentheses
Error Messages
Problem
A nutrition app searching USDA FoodData Central with the common dataType filter (Foundation, SR Legacy, Branded) cannot find homemade/as-eaten mixed dishes ("lasagna with meat", "beef stew") — those live only in the "Survey (FNDDS)" dataset. Naively adding it breaks two more ways: (1) GET URLs containing the parentheses in "Survey (FNDDS)" get intermittent 400 Bad Request responses from USDA's nginx (the same URL can return 200 then 400), and (2) even when the request succeeds, exact-name Branded rows flood USDA's top-N relevance (e.g. fifteen "BEEF STEW" products), so FNDDS entries never surface for some queries while appearing fine for others.
Solution
Three parts: (1) Add "Survey (FNDDS)" to dataType — it carries as-eaten mixed dishes with full per-100g nutrient profiles (~65 nutrients). (2) Send the search as POST with a JSON body ({query, pageSize, dataType: [...], requireAllWords, brandOwner}) instead of GET query params — the body form never hits the nginx parens issue, and brandOwner/requireAllWords work as body fields. (3) For unbranded searches, run two parallel queries — the general pool AND a Survey-only query — then merge by fdcId, letting your own ranker order the union. This makes FNDDS coverage deterministic instead of dependent on USDA's interleaving. Bonus: FNDDS/SR foods have no servingSize field but carry ranked household measures in foodMeasures; take the rank-1 entry's gramWeight (skipping "Quantity not specified") to get realistic serving weights ("1 piece 1/6 of 8-inch square" = 206 g of lasagna).
Why
FNDDS is USDA's dietary-survey database of foods as actually eaten (mixed/home-cooked dishes), separate from the lab-analyzed Foundation/SR Legacy and label-derived Branded datasets, so dataType filters written for packaged/whole foods silently exclude it. The 400s come from USDA's front-end proxy mishandling parentheses in query strings (encoded or not) on some backends; POST bodies bypass URL parsing. The flooding is USDA's relevance scoring favoring exact description matches, which branded products dominate.
Gotchas
- encodeURIComponent leaves parens unencoded, so the flaky-400 GET bug is easy to misdiagnose as an encoding mistake
- rank-1 foodMeasures can be tiny units (1 chip = 2 g) — correct data but odd as a default serving
- provider failures swallowed by Promise.allSettled make the intermittent 400s look like empty results
Context
Building food search over USDA FoodData Central /v1/foods/search when coverage of homemade/restaurant-style mixed dishes matters
Revisions (0)
No revisions yet.