Free plan

Nutrition API

Go beyond the headline macros. Pull a detailed breakdown of 40+ nutrients per product — energy, protein, carbs, fats, sugars, fiber, sodium, vitamins, and minerals — all per 100g, for any food by ID.

200 OK
GET /v1/foods/{id}/nutrients
curl -H "X-API-Key: food_•••" \
  "https://foodbase.dev/v1/foods/62ea12b8.../nutrients"

{
  "energy_kcal": 42,
  "proteins_g": 0,
  "carbs_g": 10.6,
  "sugars_g": 10.6,
  "fat_g": 0,
  "saturated_fat_g": 0,
  "fiber_g": 0,
  "sodium_mg": 4,
  "calcium_mg": 0,
  "potassium_mg": 0
}

Prefer to click around? Try the product explorer or the photo playground.

What you get back

Macros

Energy, protein, carbs, sugars, fat, saturated fat, fiber.

Minerals

Sodium, calcium, iron, potassium, magnesium, zinc, and more.

Vitamins

A, C, D, E, and the B-vitamins where reported.

Per 100g

Consistent basis so you can scale to any serving size.

40+ fields

One object with every nutrient the source data provides.

Included on the free plan — 25 nutrient breakdowns/day, no credit card required; unlimited from Starter. See plans →

Code samples

The same request in curl, JavaScript and Python. Replace YOUR_API_KEY with a key from your dashboard.


              curl "https://foodbase.dev/v1/foods/62ea12b8-b260-c288-5c38-3a4906f5d6dd/nutrients" \
  -H "X-API-Key: YOUR_API_KEY"
            

How it works

One request, five groups, per 100 g

GET /v1/foods/{id}/nutrients takes the food's UUID in the path and nothing else: no query parameters, no body, no lang. The id is the same one every other endpoint returns, so a hit from the Food Search API or the Barcode Lookup API becomes a full breakdown with one more call. Ids are deterministic across re-imports, so you can store them and keep calling this URL.

The response is a flat object with food_id plus five fixed groups: macros, fats, minerals, vitamins and other. Every key is always present and every value is either a number or null. Counting the keys in the current schema gives 41 nutrient fields, which is where the 40+ figure comes from. Units are in the key name (energy_kcal, sodium_mg, selenium_mcg, vitamin_a_iu), so you never carry a separate unit table.

All values are per 100 g of the product as sold. To show a serving, multiply by grams divided by 100. The food record's serving_size_raw is label text (for the sample Coca-Cola record it is "1 can (330 ml)"), so the conversion from that text to grams is yours to make.

  • macros (10 keys) covers energy in kcal and kJ, protein, carbs, fat, sugars, fiber, alcohol, starch and polyols; fats (7) adds saturated, trans, mono- and polyunsaturated, omega3, omega6 and cholesterol_mg
  • minerals (12) runs from sodium_mg and salt_g to selenium_mcg and iodine_mcg; vitamins (11) reports A and D in IU, B9 and B12 in mcg, the rest in mg; other holds added_sugars_g

Reading nulls and zeros correctly

The sample response for food 62ea12b8-b260-c288-5c38-3a4906f5d6dd shows the typical Open Food Facts pattern: energy_kcal 42, energy_kj 180.2, carbs_g 10.6, sugars_g 10.6, added_sugars_g 5.3, explicit zeros for proteins_g, fat_g, saturated_fat_g, sodium_mg and salt_g, and null for everything else, from fiber_g to the whole vitamins group.

Treat the two states differently. A 0 is a value the source reported; a null means the nutrient is not on the label or not in the source. Never coerce null to 0 before summing a day's intake, or a meal built from partially labeled products will silently under-report vitamins and minerals. Show a dash or "not reported" instead.

Coverage follows provenance. Rows with source usda_foundation or usda_sr_legacy are USDA laboratory analyses and populate the vitamin and mineral groups, so most keys are numbers. Rows with source usda (USDA Branded Foods) and rows from Open Food Facts are transcribed from packaging, so they are reliable for the mandatory label nutrients and often null beyond them. The source field on the food record tells you which case you are in before you render the table.

  • Render null as unknown, not as zero; only aggregate keys that are non-null across every item in the meal.
  • sodium_mg and salt_g both exist because labels differ by region; use whichever your audience expects and fall back to the other.

When you actually need this call

Every food record from the Food Database API and every barcode match already includes nutrition_summary with six headline values: energy_kcal, proteins_g, carbs_g, fat_g, sugars_g and fiber_g. Search hits are lighter rows without it. For a scanner result, a detail card or a quick calorie log, that object is usually enough and costs no nutrient breakdown.

Reserve /nutrients for the detail screen or the moment a user asks about sodium, saturated fat, cholesterol or a specific vitamin. Search or scan, show the summary, fetch the breakdown on tap. On the Free plan this is also what keeps you inside the 25-per-day allowance while you build.

The captured response headers carry no Cache-Control, so caching is your job. Ids are stable and values only change when a product is re-imported, so a cache keyed on food_id and held for hours or days is safe.

The Free-plan allowance, header by header

On the Free plan each successful breakdown draws down a separate daily counter of 25, reported on every response as X-RateLimit-Nutrients-Limit, X-RateLimit-Nutrients-Remaining and X-RateLimit-Nutrients-Reset. Read Remaining before a burst of detail fetches and you can fall back to nutrition_summary instead of hitting the wall mid-session.

At zero the endpoint returns 429 with error "Nutrients daily allowance exceeded", an upgrade_url in the body, and Retry-After holding the seconds until midnight UTC. A rejected call consumes neither a breakdown nor a daily request, and does not move the reset. Paid plans have no such counter; their nutrient calls are bounded only by the plan's daily request quota (shared across all keys on the account) and the per-key per-second limit.

If you are on Starter or above and working through a large catalog, fetch the base records through the Bulk Food Lookup API first and call /nutrients only for the ids whose summary is not enough.

Build with it

Calorie & macro trackers

Log accurate per-serving nutrition from a single lookup.

Meal planning & diets

Build plans that hit macro and micronutrient targets.

Health & clinical tools

Surface detailed nutrient profiles for dietary guidance.

Limits by plan

Free 25 nutrient breakdowns/day (X-RateLimit-Nutrients-* headers), inside 100 requests/day; 1 req/s, burst 5
Starter Unlimited breakdowns within 5,000 requests/day; 5 req/s, burst 25
Pro Unlimited breakdowns within 25,000 requests/day; 20 req/s, burst 100
Enterprise Unlimited breakdowns within 100,000 requests/day; 50 req/s, burst 250

Every response carries X-RateLimit-* headers with the remaining budget; every 429 carries Retry-After. Compare plans →

Errors and how to handle them

404 — There is no nutrients row for that id: the food may not exist, or it exists without any nutrition data (its nutrition_summary is null). The body reads "No nutrient data for this food".

Do not retry. Show the food without a breakdown; if the id came from an old cache, re-resolve it by barcode or search, since a 404 here is not transient.

422 — The id in the path is not a UUID, such as a barcode or a truncated id copied from a sample.

Pass the id field from a search, barcode or food record; resolve barcodes through the Barcode Lookup API first.

429 — Free plan only: the body reads error "Nutrients daily allowance exceeded" and X-RateLimit-Nutrients-Remaining is 0. No breakdown was consumed.

Honor Retry-After (seconds until midnight UTC), fall back to nutrition_summary for the rest of the day, and surface the upgrade_url from the body if the user needs full breakdowns now.

429 — Any plan: the per-key per-second limit or the account-wide daily request quota is exhausted; X-RateLimit-Second-Remaining and X-RateLimit-Daily-Remaining tell you which.

Wait the Retry-After seconds. Queue detail fetches instead of firing them in parallel; on a daily hit, stop until X-RateLimit-Daily-Reset.

Compared to other APIs

Most nutrition APIs either return a per-serving table you cannot rebase, or spread nutrients across name/value/unit rows you have to normalize yourself. FoodBase returns a fixed, typed object per 100 g with the unit in each key, so a client written once against the schema fits every product. The trade-off is honesty about gaps: a label-derived Open Food Facts row has nulls where a USDA row has numbers, and the API reports that rather than filling in estimates. If you only need the headline macros, the food record already carries them and this endpoint is optional.

See the full comparison of food database APIs →

Frequently asked questions

Are the values per serving or per 100 g? +

Always per 100 g, for every plan and every source. The food record's serving_size_raw is label text such as "1 can (330 ml)", so converting it to grams and scaling happens in your code. That keeps two products comparable without knowing how each brand defines a portion.

What does a null value mean, and how is it different from 0? +

Null means the nutrient is not on the label or not in the source; 0 means the source reported zero. In the sample Coca-Cola response, fat_g is 0 while fiber_g is null. Never treat the two the same when totaling a meal.

Why does a USDA food have vitamins filled in while a supermarket product does not? +

USDA Foundation and SR Legacy rows (source usda_foundation, usda_sr_legacy) come from laboratory analysis and typically populate the vitamin and mineral groups. USDA Branded rows (source usda) and Open Food Facts rows are transcribed from packaging, which in most regions only mandates the headline nutrients. Check the source field on the food record to know which kind of row you have.

Do I need this endpoint if I already have nutrition_summary? +

Often not. nutrition_summary on the food record already gives energy_kcal, proteins_g, carbs_g, fat_g, sugars_g and fiber_g. Call /nutrients when you need the rest, such as saturated fat, sodium, cholesterol or any vitamin or mineral, which is usually only on a detail screen.

How does the Free-plan limit of 25 breakdowns relate to the 100 requests per day? +

The 25 is a separate counter reported in the X-RateLimit-Nutrients-* headers and reset at midnight UTC; the 100 is the account-wide request quota. A successful breakdown draws on both. A 429 for the nutrients allowance consumes neither, but it keeps failing until the reset, so stop calling until then.

Which endpoint gives me the id to pass here? +

Any of them. The Food Search API returns ids for text queries and the Barcode Lookup API resolves a GTIN to one record. The sample food_id 62ea12b8-b260-c288-5c38-3a4906f5d6dd is the record behind barcode 5449000000996.

Start building with the Nutrition API

Free to start. No credit card required.

Get API key Go to Dashboard