USDA FoodData Central API: getting started
API key, endpoints, rate limits, and the response shape you'll actually deal with.
What FDC is
USDA FoodData Central (FDC) is the US government’s nutrition database. It’s free, well-maintained, and bulk-downloadable. It’s also the canonical source for any FOSS calorie tracker that wants real macro and micronutrient data for US foods. Open Food Facts has packaged-product coverage; FDC has analytical coverage of raw and prepared foods.
Five sub-databases inside FDC, accessed through the same API:
- Foundation Foods: small set, very high quality, full analytical work.
- SR Legacy (Standard Reference 28, the old NDB): large, retired but still authoritative for raw foods.
- Survey (FNDDS): foods as eaten, used in dietary surveys.
- Branded Foods: manufacturer-submitted label panels.
- Experimental Foods: USDA agricultural research.
Getting an API key
Visit api.data.gov/signup. Free, no payment, takes one form and one email confirmation. Within a few minutes you’ll have a key.
The data.gov umbrella key works for FDC and several other federal APIs. Don’t lose it; rate limits are tracked against the key.
Rate limits
As of 2025–2026:
- 1,000 requests per hour per IP, default.
- Higher limits available on request via the FDC contact form for legitimate research / open-source uses.
In practice, 1,000/hr is generous for a personal tracker. It is not generous for a multi-user app querying live; cache aggressively.
Endpoints
Three endpoints you’ll actually use:
/foods/search
curl "https://api.nal.usda.gov/fdc/v1/foods/search?api_key=YOUR_KEY&query=banana&pageSize=5"
Returns up to 200 results. Pagination via pageNumber. Filter by dataType (e.g. dataType=Foundation,SR Legacy).
/food/{fdcId}
curl "https://api.nal.usda.gov/fdc/v1/food/173944?api_key=YOUR_KEY"
Returns the full record for one food item by FDC ID. Includes foodNutrients array with every measured nutrient.
/foods (batch lookup)
curl -X POST "https://api.nal.usda.gov/fdc/v1/foods?api_key=YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"fdcIds":[173944,1750340,2055170]}'
Up to 20 IDs per request. Saves on rate limit when you’re hydrating a list.
Response shape
The fields you’ll actually use, simplified:
{
"fdcId": 173944,
"description": "Banana, raw",
"dataType": "SR Legacy",
"foodNutrients": [
{ "nutrient": { "id": 1003, "name": "Protein", "unitName": "g" }, "amount": 1.09 },
{ "nutrient": { "id": 1005, "name": "Carbohydrate, by difference", "unitName": "g" }, "amount": 22.84 },
{ "nutrient": { "id": 1004, "name": "Total lipid (fat)", "unitName": "g" }, "amount": 0.33 },
{ "nutrient": { "id": 1008, "name": "Energy", "unitName": "kcal" }, "amount": 89 }
],
"foodPortions": [
{ "amount": 1, "modifier": "medium", "gramWeight": 118 }
]
}
The “amount” is per 100g for raw foods. Always per 100g for SR Legacy and Foundation. Branded Foods sometimes use serving sizes; check servingSize and servingSizeUnit on the record.
A working Python script
import requests
import os
API_KEY = os.environ['USDA_API_KEY']
BASE = "https://api.nal.usda.gov/fdc/v1"
def search(query, page_size=10):
r = requests.get(
f"{BASE}/foods/search",
params={
"api_key": API_KEY,
"query": query,
"pageSize": page_size,
"dataType": "Foundation,SR Legacy",
},
timeout=10,
)
r.raise_for_status()
return r.json()['foods']
def get_food(fdc_id):
r = requests.get(
f"{BASE}/food/{fdc_id}",
params={"api_key": API_KEY},
timeout=10,
)
r.raise_for_status()
return r.json()
def calories_per_100g(food):
for n in food.get('foodNutrients', []):
nut = n.get('nutrient', {})
if nut.get('name') == 'Energy' and nut.get('unitName') == 'kcal':
return n.get('amount')
return None
if __name__ == "__main__":
results = search("banana")
for f in results:
kcal = calories_per_100g(f)
print(f"{f['fdcId']:>10} {f.get('description','')[:50]:50} {kcal} kcal/100g")
Run with USDA_API_KEY=... python script.py. Output:
173944 Banana, raw 89 kcal/100g
1102655 Banana, raw, frozen, sliced 90 kcal/100g
...
Common gotchas
dataTypematters. Without filtering, your search returns Branded results that drown out the analytical ones. We almost always filter to Foundation + SR Legacy for general searches; Branded only when scanning a barcode.- Energy unit is
kcal. There’s also akJenergy entry. Filter onunitName. amountis per 100g for analytical foods. Don’t multiply by serving size unless the record says so.- Some “foods” return 0 calories. Usually water, sometimes a stub record. Filter on
kcal > 0if appropriate. - Rate limit headers. Read
X-RateLimit-Remainingon every response.
Bulk downloads
If you’re going to query more than a few thousand records a day, switch from API to bulk. FDC publishes CSV and JSON dumps monthly at fdc.nal.usda.gov/download-datasets.html. About 6 GiB total. We have a Postgres-loader walkthrough for offline use.
When to use which sub-database
| You want | Query |
|---|---|
| Calorie of “banana” | Foundation + SR Legacy |
| Macros of a packaged Cheerios | Branded |
| What was eaten in NHANES surveys | Survey (FNDDS) |
| Latest USDA research foods | Experimental |
| Everything (search-style) | unfiltered, then filter results |
References
- USDA FDC API: fdc.nal.usda.gov/api-guide
- API key signup: api.data.gov/signup
- FDC bulk → Postgres
- Foundation vs Survey vs Branded