#usda#api#fooddata-central#tutorial

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

  • dataType matters. 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 a kJ energy entry. Filter on unitName.
  • amount is 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 > 0 if appropriate.
  • Rate limit headers. Read X-RateLimit-Remaining on 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 wantQuery
Calorie of “banana”Foundation + SR Legacy
Macros of a packaged CheeriosBranded
What was eaten in NHANES surveysSurvey (FNDDS)
Latest USDA research foodsExperimental
Everything (search-style)unfiltered, then filter results

References