Dietary Supplement Analysis (NIH data)
Checks a supplement routine against NIH data: finds each product's label (by name, barcode or NIH ID), totals every vitamin and mineral per day, and flags amounts above NIH upper limits, medication interactions from the NIH fact sheets, herbs with NIH safety signals, and FDA recalls, with a plain-language summary (English or Spanish) and a source for every number. Free trial, no key needed.
Hosted MCP Server
npx add-mcp 'https://agents.trackmysupplement.com/mcp'Installs into Claude Code, Codex, Cursor and more
Documentation
Dietary Supplement Label and Nutrient Intake Analysis
AI assistants call this service when someone asks "is my supplement routine OK?". Give it the products the way a person names them (product name, barcode or NIH label ID), with age and sex, and optionally their medications, conditions and what they eat. It finds each label in the NIH Dietary Supplement Label Database, totals every vitamin and mineral per day across all of them, and compares the totals with NIH reference intakes and upper limits. It also reports the medication interactions named in the NIH fact sheets, herbs with NIH safety signals, and FDA recalls and adverse event reports. Every answer starts with a plain-language summary an assistant can relay (English or Spanish), and every number traces back to its label or NIH source.
It runs on Pocket Network Beta TestNet as the paid service dietary-supplement-analysis, and as a remote MCP server with a free trial.
| Try it | For judges: try it in one minute |
| Pocket Network | Service ID dietary-supplement-analysis on Beta TestNet, relay endpoint https://pokt-beta.trackmysupplement.com. Passes all 9 Pocket portal acceptance rules, with a paid relay settled on-chain (record) |
| MCP server | https://agents.trackmysupplement.com/mcp, listed in the official MCP Registry and on Smithery. See mcp/README.md |
| Accuracy | 56 hand-worked cases, all passing (EVAL.md) |
| Running version | /v1/version; every release is in CHANGELOG.md |
| Run a supplier or gateway | OPERATORS.md: a multi-arch image is published for every release |
Data sources, all public:
- Labels: NIH Dietary Supplement Label Database (DSLD), API v9 plus a weekly mirror of NIH's full download (214,782 labels).
- Reference values: Food and Nutrition Board DRIs, as published on the NIH Office of Dietary Supplements fact sheets.
- Interactions and herbs: NIH ODS and NCCIH fact sheets; medication names are normalized with NIH RxNorm.
- Recalls and adverse events: openFDA.
- Foods: USDA SR28 (FoodData Central "SR Legacy"); typical US intake from What We Eat in America, NHANES 2017–March 2020.
What it is not: medical advice or a diagnosis. It reports what labels declare, compares the totals with published reference values, and relays the interactions and safety statements NIH publishes. The interaction list is not exhaustive, and no flag means nothing was found to report, not an all-clear.
For judges: try it in one minute
| Service ID | dietary-supplement-analysis (Pocket Network Beta TestNet, rpc_type: REST, 500 compute units per relay) |
| Relay endpoint (staked supplier) | https://pokt-beta.trackmysupplement.com. Accepts signed Pocket relays only. |
| Demo endpoint (direct, free trial) | https://agents.trackmysupplement.com. The same backend without the relay layer, so you can use curl. Up to 30 requests a minute and 100 calls a day per client (details); over a limit it returns 429 with "code":"RATE_LIMITED" and a Retry-After header. |
Paying agents call the service through Pocket Network: with an application staked for dietary-supplement-analysis on Beta, relay with pocket-ap as shown in Through Pocket Network. The paths and request bodies below are the same either way; the demo endpoint just skips the relay layer so you can use curl without staking.
Each example is one call, made the way an assistant would make it after a user describes their routine in plain words. jq -r .agent_summary prints the answer the assistant relays; a trimmed copy of the live answer is shown under each one.
1. "I'm 74 and on Coumadin. I take Centrum Silver, fish oil and ginkgo."
curl -s https://agents.trackmysupplement.com/v1/stack/analyze -H 'Content-Type: application/json' -d '{
"age": 74, "sex": "female", "detail": "summary", "medications": ["Coumadin"],
"products": [{"query": "Centrum Silver Women 50+"}, {"query": "Nature Made Fish Oil 1200 mg"}, {"query": "Ginkgo Biloba 120 mg"}]}' | jq -r .agent_summary
Ginkgo (in Nutricost Ginkgo Biloba 120 mg): NCCIH: ginkgo may increase the risk of bleeding in people taking anticoagulants such as warfarin. Coumadin with Vitamin K in this stack: Vitamin K counteracts warfarin; NIH describes a serious and potentially dangerous interaction. Coumadin with Vitamin E in this stack: … Coumadin with EPA/DHA in this stack: High-dose fish oil may prolong clotting time and raise INR … 1 more interaction finding in interactions.findings.
The brand name is recognized as warfarin, three products are found by name in the NIH label database, and every warning cites its NIH or NCCIH fact sheet.
2. "I'm pregnant. I take a prenatal, vitamin A 25,000 IU for my skin, and ashwagandha for stress."
curl -s https://agents.trackmysupplement.com/v1/stack/analyze -H 'Content-Type: application/json' -d '{
"age": 29, "sex": "female", "life_stage": "pregnancy", "detail": "summary",
"products": [{"query": "Nature Made Prenatal Multi + DHA"}, {"query": "vitamin A 25000 IU"}, {"query": "ashwagandha root extract"}]}' | jq -r .agent_summary
Vitamin A: 7500 mcg a day vs a UL of 3000 mcg (250%) … The UL guards against liver damage, and birth defects when taken in pregnancy (preformed vitamin A only). Leaving out … Vitamin A 25000 IU would bring Vitamin A to 0 mcg. Ashwagandha: … NCCIH: avoid ashwagandha during pregnancy and while breastfeeding.
3. "I'm 62: Centrum Silver, fish oil, magnesium and zinc. Should I add vitamin D 5000?" With "diet": "typical", food is counted too.
curl -s https://agents.trackmysupplement.com/v1/stack/analyze -H 'Content-Type: application/json' -d '{
"age": 62, "sex": "female", "diet": "typical",
"products": [{"query": "Centrum Silver Women 50+"}, {"query": "Nature Made Ultra Fish Oil 1400"},
{"query": "KAL Magnesium Glycinate 400"}, {"query": "Thompson Zinc 50 mg", "servings_per_day": 1}],
"candidates": [{"query": "vitamin d3 5000 iu"}]}' | jq -r .agent_summary
Zinc: 65 mg a day vs a UL of 40 mg (163%), mostly from Thompson Zinc 50 mg. The UL guards against reduced copper status … Magnesium: 500 mg a day vs a UL of 350 mg (143%) … Leaving out Thompson Zinc 50 mg would bring zinc to 15 mg (37.5% of the UL). … Adding … Vitamin D3 5000 IU would put Vitamin D (150% of the UL) above the upper limit.
The answer gives the one change that fixes it, and the what-if answers the question the user actually asked.
4. In Spanish: "Tomo Centrum Silver y Caltrate; tomo warfarina y levotiroxina."
curl -s https://agents.trackmysupplement.com/v1/stack/analyze -H 'Content-Type: application/json' -d '{
"age": 68, "sex": "female", "lang": "es", "detail": "summary", "medications": ["warfarina", "levotiroxina"],
"products": [{"query": "Centrum Silver Women 50+"}, {"query": "Caltrate 600+D3"}]}' | jq -r .agent_summary
Warfarina con vitamina K de este conjunto: La vitamina K contrarresta la warfarina … Levotiroxina con calcio de este conjunto: … No tome la levotiroxina dentro de las 4 horas del carbonato de calcio. Levotiroxina con hierro de este conjunto: …
5. Scan a bottle: a barcode finds the label, and the same barcode finds FDA recalls and adverse event reports.
curl -s https://agents.trackmysupplement.com/v1/products/lookup -H 'Content-Type: application/json' -d '{"upc":"031604026974"}' | jq -r '.product.brand + " " + .product.name'
curl -s https://agents.trackmysupplement.com/v1/products/fda -H 'Content-Type: application/json' -d '{"upc":"031604026974"}' | jq -r .agent_summary
For a product with recalls, try {"query":"Nature Made Vitamin D3 1000 IU"}: FDA lists 4 matching recalls.
6. Agent to agent: much of the use is expected to come from other agents doing a bigger job, with no person typing: a pharmacy agent checking a new prescription against the supplements on file, a shopping agent checking a cart before checkout, a vision agent passing along a label it read from a photo, a coaching agent's weekly check, and a medication-review agent combining this service with drug-drug services. examples/ has all five as one script (bash examples/agent-flows.sh), plus a 50-line assistant that loads the tools from GET /v1/tools and lets a model call them (agent_loop.py).
More calls: POST /v1/nutrients/reference (RDA and UL for one nutrient, offline), POST /v1/products/search (find labels), POST /v1/interactions/check (medications without a stack), and GET /v1/tools, GET /v1/guide and GET /openapi.json for agent frameworks. All are listed under Endpoints.
Through Pocket Network (paid relay)
You need an application staked for dietary-supplement-analysis on Beta TestNet. Fund an address from the Beta faucet and stake it as an application with at least 1,000 POKT. Then install pocket-ap and relay with this repo's client config:
export POCKET_APP_PRIVATE_KEY=<your app key, 64 hex> # pocketd keys export <name> --unarmored-hex --unsafe
pocket-ap call --config deploy/beta-stake/pocket-ap.yaml \
--service dietary-supplement-analysis --rpc-type rest \
-X POST --path /v1/stack/analyze \
-d '{"age":34,"sex":"female","products":[{"name":"Custom magnesium","ingredients":[{"name":"magnesium","amount":250,"unit":"mg"}]}]}' -v
On-chain record (Beta TestNet)
| Step | Tx hash | Block |
|---|---|---|
| Service registered, with the card from card/card.json | BC01B11F…7DD5 | 722359 |
| Supplier staked, 60,000 POKT | 9CBCBAAF…51FA | 722747 |
| Test application staked, 1,100 POKT | 6734D5F4…05EE | 722744 |
| Claim for the first paid relay (settled at block 722813) | F3D81B3F…88EB | 722793 |
Claim for a relay of the judges' example A on v0.12.6, sent with pocket-ap from the owner's Mac on 2026-10-11 (settled; 500 compute units debited from the app stake) | A9A5B384…3DA5 | 724433 |
Details and the server setup are in deploy/PHASE4_READINESS.md.
Why it matters for Pocket Network
- A question millions of people have. 60.2% of US adults took a dietary supplement in the past 30 days, 38.7% took two or more, and 75.9% of adults 60 and older took at least one (CDC NCHS, 2021–2023). Health, pharmacy, grocery and fitness assistants all get asked whether a routine is OK, and a good answer needs the exact amounts on each label and the NIH limits for that person, which a model alone doesn't have.
- Repeat calls, many from other agents. Each call is one relay (500 compute units). Pharmacy, shopping, coaching and medication-review agents call it on every new prescription, cart, photo or weekly check, with no person in the loop (examples).
- Fills a gap next to Pocket's own health services. Pocket's catalog has drug services (
fda,med-safety,clinical-trials) but nothing for supplements, which most of those users also take. An agent that already calls those services for prescriptions can call this one for everything else in the cabinet. - A free trial that leads to paid relays. The MCP server is listed in the official MCP Registry, on Smithery and on mcpservers.org. Its free trial (25 checks a day per connection) is enough for a person to try it, and every limit message points agents with real volume to the Pocket route, which is never metered.
- Ready for more suppliers. A multi-arch image is published for every release, OPERATORS.md sets up a supplier in about 5 minutes, and responses never use 5xx, so gateways don't mistake a bad input for a faulty supplier.
- Checked. 56 hand-worked cases pass on every release (EVAL.md), and the live service is tested every 30 minutes against the public endpoint and the Pocket portal audit.
Endpoints
All inputs come from the JSON request body. All responses are JSON objects, errors included.
| Method | Path | Purpose |
|---|---|---|
| GET/HEAD | / | Service identity |
| GET | /v1/version | {"service":"dietary-supplement-analysis","version":"0.12.7"} |
| GET | /v1/health | {"status":"ok"} |
| GET | /openapi.json | OpenAPI 3.1 spec (also at openapi/openapi.json) |
| POST | /mcp | Remote MCP server (Streamable HTTP, stateless): the same tools for MCP clients |
| GET | /v1/provenance | Code version, git commit and the edition of each data source |
| GET | /v1/tools | Tool definitions (name, description, JSON Schema input_schema) for function calling |
| GET | /v1/guide | Usage guide for agents, as markdown inside JSON (same text as llms.txt) |
| POST | /v1/stack/analyze | One call: per-nutrient totals across 1–15 products (names, barcodes, IDs or custom), flags, fixes, what-if candidates |
| POST | /v1/interactions/check | NIH-listed nutrient interactions for medications or conditions, no stack needed: {"medications":["Synthroid"],"nutrients":["calcium"]} |
| POST | /v1/products/search | Find products in DSLD: {"query":"vitamin d3 5000","brand":"NOW","limit":5}; label versions collapsed |
| POST | /v1/products/lookup | Label for a barcode: {"upc":"031604026974"} |
| POST | /v1/products/fda | FDA recalls and adverse event reports (openFDA) for one product: {"query":"Nature Made D3 1000 IU"}, {"upc":...}, {"dsld_id":...} or {"brand":...,"name":...} |
| POST | /v1/products/get | Normalized labels: {"dsld_id":12345} or {"dsld_ids":[...]} (max 10) |
| POST | /v1/nutrients/reference | RDA/EAR/AI/UL and UL basis for one nutrient: {"nutrient":"vitamin d","age":34,"sex":"female"} (offline) |
| POST | /v1/tool | MCP-style dispatcher: {"tool":"stack_analyze","arguments":{...}} |
POST /v1/stack/analyze
{
"age": 34,
"sex": "female",
"products": [
{ "query": "Centrum Silver Women 50+" },
{ "upc": "031604026974", "servings_per_day": 1 },
{ "dsld_id": 67890 },
{ "name": "Homemade magnesium", "ingredients": [ { "name": "magnesium glycinate", "amount": 200, "unit": "mg", "nutrient_basis": "elemental" } ] }
],
"medications": [ "warfarin", "Synthroid 50 mcg" ],
"conditions": [ "smoker" ],
"candidates": [ { "query": "vitamin d3 5000 iu" } ]
}
- Products (1–15) can be a product name (
query, optionally withbrand), a barcode (upc), a DSLD ID, or a custom product with explicit ingredients. Custom ingredients may give aform(for example"folic acid"or"d-alpha tocopherol") so IU and DFE conversions can be applied. - Names and barcodes are resolved server-side. A name is matched against DSLD search results by the share of its words found in the brand and product name; label versions of the same product are collapsed and the newest on-market one is used.
resolutions[]records each match withconfidence(high: every word matched one product;medium: every word matched but other products did too, or most words matched;low: under 60%) and up to three alternatives. A product that can't be identified is reported (PRODUCT_NOT_RESOLVED) and left out; the call fails with 404 only when nothing could be identified. diet: "typical"adds the US average daily intake from food and drinks for the age and sex (What We Eat in America, NHANES 2017–March 2020) and repeats the comparisons inwith_typical_diet. Food counts toward a UL only where the UL covers it: all of it for total-intake ULs, food folic acid for folate, retinol for vitamin A, added vitamin E for vitamin E, none for magnesium. It isn't applied under age 2 or in pregnancy and lactation.medications(up to 30, generic or brand names as the user gives them) are matched to 46 drug classes and checked against the 55 medication interactions listed in the NIH Office of Dietary Supplements fact sheets (data/interactions.json). Names the list doesn't know are normalized to their ingredient with NIH RxNorm (RxNav) when the service can reach it; matching is whole-word, never fuzzy.interactions.findings[]gives each interaction's kind (supplement_changes_drug,drug_lowers_nutrient,drug_raises_nutrient,additive_risk,two_way), NIH's timing advice where it gives any, the fact sheet, andin_stackwith the products involved; in-stack findings come first and leadagent_summary. The list is not exhaustive (INTERACTIONS_PARTIAL), and medication names are never logged.- Botanicals. Herbs and other non-nutrient ingredients with NIH safety signals (16 so far: ashwagandha, kava, St. John's wort, ginkgo, green tea extract, turmeric/curcumin, black cohosh, garcinia cambogia, red yeast rice, goldenseal, berberine, yohimbe, ephedra, valerian, echinacea, creatine) are reported in
botanicals[]with the NCCIH or ODS statement, the pregnancy statement, and dose checks where NIH cites doses (creatine above 5 g and 25 g a day, EGCG at 843 mg a day, valerian above 600 mg a day). They're matched on DSLD's ingredient group or the exact name after dropping plant-part and preparation words ("Ashwagandha Root Extract" is ashwagandha), never by substring (data/botanicals.json). Withmedications, botanical-drug pairs NIH names (St. John's wort with birth control, warfarin or SSRIs; ginkgo with anticoagulants; kava and valerian with sedatives; red yeast rice with statins; goldenseal with metformin) become interaction findings. conditions:smoker(current or former, or asbestos exposure: beta-carotene from 20 mg a day, counting vitamin A declared as beta-carotene),kidney_disease(potassium, magnesium, phosphorus),hemochromatosis(iron; vitamin C from 500 mg),lab_test_soon(biotin from 1 mg skews thyroid, troponin and other tests),cancer_treatment(high-dose vitamin C and E).foods(up to 30) lists what the person eats in a day:{"query": "spinach cooked", "amount": 1, "unit": "cup"}or{"query": "whole milk", "grams": 488}or{"ndb_number": "11458", "grams": 180}. Foods are matched offline against the 8,789 foods of USDA SR28 (FoodData Central's "SR Legacy"), using whole words and the USDA household portions, and added to the supplement totals innutrient_totals[].with_foods. Nutrients only the foods supply are checked infoods.totals. Food counts toward a UL only where the UL covers food, as fordiet. Nutrients USDA didn't analyze for a food are listed, not assumed zero; three SR28 manganese values that are off by orders of magnitude are excluded and reported. Usefoodsordiet: "typical", not both.timinglists the NIH timing statements that apply to the stack, each naming its products and fact sheet: calcium in doses of 500 mg or less, calcium carbonate with a meal, separate calcium and iron supplements, 25 mg+ iron apart from zinc, iron with food at high doses, vitamin D with some fat, green tea extract with food, and medication spacing for in-stack interactions. The first one also appears inagent_summary.lang: "es"writesagent_summaryin Spanish, with the English text kept inagent_summary_en. Every NIH phrase that can appear in a summary has a reviewed Spanish translation (data/i18n-es.json); other fields stay in English. Medication names can be given in Spanish (warfarina, levotiroxina, omeprazol) or as Spanish and Latin American brands (Sintrom, Eutirox), with or without accents.detail: "summary"returns only what an agent needs to answer (agent_summary,flaggednutrients,suggestions,resolutions, candidate verdicts,data_gap_codes,provenance), typically 1–3 KB instead of 20–40 KB.- Candidates (up to 5) are products the user is considering. Each is evaluated as an addition to the stack in
candidate_results[]and isn't counted in the totals. servings_per_daydefaults to the label's maximum daily servings, or 1 if the label doesn't say. Every default is listed inassumptions.ageandsexare optional. Without them you get totals and overlaps, but no reference comparisons.life_stage("pregnancy"or"lactation", female, ages 14–50) switches to the pregnancy or lactation reference values;"pregnant": trueand"breastfeeding": truework too. For infants, give the age as a fraction of a year (0.25is 3 months): the 0–6 and 7–12 month values apply.
The response contains, in this order: profile, summary, agent_summary, resolutions (when names or barcodes were given), assumptions, suggestions, candidate_results (when candidates were given), botanicals (when the stack has any with NIH signals), interactions (when medications or conditions were given), nutrient_totals, overlaps, unrecognized_ingredients, data_gaps, products, sources and disclaimer.
agent_summaryis two to five plain sentences, generated deterministically from the numbers below, that an agent can relay as written.suggestionslists, for each above-UL nutrient, up to three single-product changes (reduce_servingsorremove_product) with the resulting amount, whether that alone brings the nutrient to or under the UL (resolves), which other above-UL nutrients it also fixes, and which other nutrients a removal would lower. They're arithmetic, not advice.candidate_resultsgives each candidate averdict(adds_above_ul,increases_above_ul,adds_near_ulorno_new_flags), the new flags and the before and after totals.
Each nutrient total includes:
| Field | Meaning |
|---|---|
daily_total | Sum across products, in the nutrient's canonical unit (mcg RAE, mcg DFE, mg, mcg) |
complete | false when some label rows couldn't be counted (see data_gaps); the total is then a lower bound |
reference | RDA, EAR, AI and UL for the profile, with ul_scope, ul_note and ul_basis (what the UL protects against, for example "Reduced copper status" for zinc) |
amount_toward_ul | The part the UL applies to: folic acid only for folate, preformed vitamin A only for vitamin A |
percent_of_ul, percent_of_rda, percent_of_ai | Percent of each reference value. percent_of_ul is withheld when the UL portion is only partly known and the UL isn't already exceeded |
coverage | Percent of the RDA (or AI) these supplements alone supply. Informational: diet normally supplies much of it, so low coverage isn't a shortfall |
flags | above_ul (over the UL) or near_ul (90–100% of the UL). No flag means nothing to report, not a judgement about the stack |
severity | high (twice the UL or more), moderate (above the UL), low (near the UL), or null |
contributions | Each product's label amount, converted amount, servings per day and daily amount |
with_typical_diet | With "diet": "typical": food, estimated_total, amount_toward_ul, percent_of_ul, coverage and flags with US average food intake added |
Errors
{ "error": { "code": "INVALID_INPUT", "message": "products: ...", "retryable": false, "field": "products" } }
| Code | Status | When |
|---|---|---|
INVALID_INPUT | 400 | Malformed or out-of-range input |
NOT_FOUND | 404 | Unknown DSLD ID, nutrient or route |
UNSUPPORTED | 422 | Pregnancy or lactation outside ages 14–50 |
UPSTREAM_UNAVAILABLE | 424 | DSLD can't be reached and nothing is cached (retryable: true) |
INTERNAL_ERROR | 422 | Unexpected failure. Returned as 4xx because Pocket gateways treat 5xx as a supplier fault |
Data gap codes
| Code | Meaning |
|---|---|
BLEND_UNQUANTIFIED | A proprietary blend lists ingredients without their own amounts; the blend weight is not divided among them |
QUANTITY_MISSING | A recognized nutrient row has no amount |
UNIT_NOT_CONVERTIBLE | The unit can't be converted unambiguously, for example vitamin A in IU with an unknown form |
COMPOUND_WEIGHT | The amount may be compound weight, not elemental mineral |
AMBIGUOUS_BASIS | A custom ingredient named for a compound lacks nutrient_basis |
UL_PARTIAL | The UL applies to one form only (folic acid, preformed vitamin A) and some forms are unspecified |
PROFILE_MISSING | No age and sex, so no reference comparison |
PRODUCT_NOT_RESOLVED | A product name or barcode couldn't be matched to a DSLD label; it isn't counted |
DATA_SUSPECT | NIH's label record looks inconsistent; the amount is still counted. check says which test failed: duplicate_row (the same ingredient twice under one heading, like EPA entered as DHA), nested_exceeds_parent (rows add up to more than their declared total), percent_dv_mismatch (the printed %DV contradicts the amount under both the current and the pre-2016 Daily Value), implausible_amount (over 50 times the highest adult UL, typically mg typed for mcg) |
INTERACTIONS_NOT_ASSESSED | No medications were given, so medication interactions weren't checked. Supplement-to-supplement interactions are never evaluated |
INTERACTIONS_PARTIAL | Medications were checked, but only against the interactions named in the NIH ODS fact sheets; the list is not exhaustive |
FOOD_NOT_RESOLVED | A food in foods matched no USDA SR28 food and isn't counted |
FOOD_PORTION_UNKNOWN | USDA lists no portion for the unit given for that food; the listed portions are in foods.items[].portions (give grams instead) |
FDA_UNAVAILABLE | /v1/products/fda only: openFDA couldn't be reached for recalls or adverse events; the rest of the answer stands |
MEDICATION_NOT_RECOGNIZED | A medication name matched no class in the NIH fact sheet interaction list (directly or through RxNorm), so nothing was checked for it |
How the numbers are produced
Accuracy is checked against 56 hand-worked cases; see EVAL.md.
- Reading DSLD labels:
- DSLD often stores vitamin A as plain mcg or mg. It's read as mcg RAE only when the label's %DV matches the 900 mcg RAE Daily Value.
- When DSLD leaves out a unit (for example "6 NP"), it's inferred only if the label's %DV matches the FDA Daily Value; otherwise it's a gap.
- Rows nested under a nutrient break it down by form (folate 800 mcg DFE = folic acid 480 mcg). They give the exact UL portion and are never counted twice.
- Stated form shares ("40% as beta-carotene") split IU conversions exactly when they cover 100%. When they cover less, the rest is treated as an unknown form.
- Gamma and delta tocopherols and tocotrienols are not counted as vitamin E.
- Matching an ingredient to a nutrient tries, in order: the DSLD
ingredientGroup, the UNII code, an exact synonym, then an exact form name that belongs to exactly one nutrient. There's no substring or fuzzy matching, so "Vitamin B" never matches "Vitamin B12". Anything else goes tounrecognized_ingredients. - Unit conversion happens only when the result is unambiguous:
- Vitamin D: 1 mcg = 40 IU.
- Vitamin E: natural form 0.67 mg per IU, synthetic form 0.45 mg per IU. An unknown form becomes a data gap.
- Vitamin A: IU converts only for retinol (0.3 mcg RAE per IU) or supplemental beta-carotene (0.3 mcg RAE per IU).
- Folate: plain mcg of folic acid × 1.7 = mcg DFE.
- Percent DV is shown exactly as on the label and is never used in totals.
- UL scope is stored for each nutrient and explained in the response:
- Magnesium: supplements and medications only.
- Folate: folic acid only.
- Vitamin A: preformed only.
- Niacin and vitamin E: supplemental intake.
- Everything else is a total-intake UL. A supplement total under one of those doesn't show that total intake is under it, and
assumptionssays so.
- Reference data lives in data/source/manual.json. scripts/build-nutrients.ts builds data/nutrients.json from it, and from the SuppleTracker
global_compoundsexport if one is present. The build validates units, sources, age ranges and synonym collisions. It covers 26 vitamins and minerals with DRIs, plus EPA, DHA, melatonin, CoQ10 and beta-carotene, which are recognized but have no reference values.
Running locally
npm ci
npm run dev # http://localhost:8080 (live DSLD)
DSLD_FIXTURES_DIR=test/fixtures/dsld npm run dev # offline, synthetic fixture labels 900001-900005
npm run check # data + OpenAPI freshness, lint, typecheck, tests
docker compose up --build # container on 127.0.0.1:8080
| Env var | Default | Purpose |
|---|---|---|
PORT / HOST | 8080 / 0.0.0.0 | Listen address |
DSLD_API_KEY | none | An api.data.gov key, sent to DSLD as X-Api-Key (raises NIH's limit from 1,000 to 10,000 requests an hour) |
OPENFDA_API_KEY | none | The same api.data.gov key, for openFDA |
OPENFDA / RXNORM | on | off stops calls to openFDA or RxNorm (medication matching then uses only the names in data/interactions.json) |
FREE_TIER_HOSTS | none | Host names that get the free-trial quotas (the demo uses agents.trackmysupplement.com); requests on other hosts, such as Pocket relays, are never metered. FREE_TIER_PER_SESSION, FREE_TIER_PER_IP, FREE_TIER_PER_IP_MCP and FREE_TIER_GLOBAL change the quotas |
DSLD_BASE_URL | https://api.ods.od.nih.gov/dsld/v9 | DSLD API base |
DSLD_PREWARM_IDS | none | Comma-separated label IDs to cache at startup |
DSLD_STORE_PATH | /data/dsld.sqlite in the container, off otherwise | Persistent label store (SQLite). Every fetched label is kept; when NIH is unreachable, labels, barcodes and searches are answered from it. Empty turns it off |
DSLD_WARM | 1 | With the store on, a background job fetches popular products into it at about one request per second (queries in data/warm-queries.json). 0 turns it off |
DSLD_FIXTURES_DIR | none | Serve DSLD from local label-<id>.json files (dev only; test/fixtures/dsld/real holds 12 real labels) |
UPSTREAM_BUDGET_MS | 8000 | Total time one request may spend waiting on DSLD before answering 424 |
LOG_LEVEL | info | Logs record method, path, status and duration only, never request bodies |
Limits
- Up to 15 products and 5 candidates per analyze call, 10 IDs per get call, and 10 search results.
- NIH's full DSLD download (214,782 labels) is mirrored into the label store and refreshed weekly when NIH publishes a new one. Labels and barcodes in the mirror are answered without calling NIH. Name searches use the live API (with an api.data.gov key) and fall back to the mirror, switching to it automatically for 10 minutes whenever NIH rate-limits.
GET /v1/provenanceshows the mirrored version. - DSLD calls run at most 4 at a time, with a 6 s deadline each and an 8 s budget per request. Labels are cached in memory for 7 days and searches for 24 hours, and labels are also kept in the persistent label store (refreshed after 30 days). If DSLD is down, stale copies and the label store answer instead; search results from the store say
"search_source": "local". Measured against live DSLD: a cold 15-product analyze takes 1.5 s and a warm one 17 ms. - Text that Pocket gateways treat as an error signal ("timeout", "block not found" and others) is
\u-escaped in responses. It decodes to the same string. - Ages from birth to 120, plus pregnancy and lactation (14–50). Caffeine is checked against FDA guidance (400 mg a day for healthy adults, under 200 mg in pregnancy) and sodium against the 2019 Chronic Disease Risk Reduction Intake (2,300 mg), neither of which is a DRI UL; the response says so in
ul_note. - The public demo endpoint is a free trial. It allows 30 requests per minute per client, plus daily quotas that reset at 00:00 UTC:
- 25 tool calls per MCP connection (each connection gets its own
Mcp-Session-Id), at most 20 a minute; - 100 REST calls per IP;
- 1,000 MCP calls per IP (hosted clients such as claude.ai share egress addresses);
- 5,000 free calls in total.
Discovery (
GETroutes,initialize,tools/list) is free. Results are never degraded. REST responses carryX-Free-Tier-Remaining,X-Free-Tier-LimitandX-Free-Tier-Resetheaders, and MCP results carry the same in_meta.free_tier. Past a quota, REST answers429 RATE_LIMITED, and MCP returns a readable tool result saying when the quota resets and how to switch to the paid Pocket route. - Counters are salted hashes, kept for two days.
- Relays through Pocket Network are never metered.
- 25 tool calls per MCP connection (each connection gets its own
Disclaimer, terms and licenses
Information only, not medical advice. The service reports what supplement labels declare and how the totals compare with reference values, interactions and safety statements published by NIH and FDA. It is not a diagnosis or treatment recommendation, is not a substitute for a doctor or pharmacist, and is not intended to diagnose, treat, cure or prevent any disease. Labels and public data can be wrong or out of date, the interaction list covers only what NIH names, and no flag is not an all-clear. Check with a health professional before starting, stopping or changing a supplement or medication; in an emergency, call 911 or Poison Control (1-800-222-1222) in the US. Every response carries this in its disclaimer field and in the last sentence of agent_summary, and apps and agents that relay results agree to pass it on.
Use of the hosted service is governed by the terms of use. NIH, FDA and USDA do not endorse this service, and brand names are used only to identify products.
The code is under the MIT License. The data it serves from DSLD, ODS, NCCIH, RxNorm, openFDA and USDA is U.S. Government work in the public domain; the curated interaction and herb files in data/ cite their NIH sources.