Food Categories API
Browse every food category with its product count — the backbone for filters, facets, and navigation. Paginated JSON over the full catalog, served from a precomputed view so it stays fast.
curl -H "X-API-Key: food_•••" \
"https://foodbase.dev/v1/categories"
{
"data": [
{ "category": "Beverages", "product_count": 142853 },
{ "category": "Snacks", "product_count": 98211 },
{ "category": "Dairy", "product_count": 76540 }
],
"total": 306609
} Prefer to click around? Try the product explorer or the photo playground.
What you get back
category
The category name.
product_count
How many products fall under it.
total
Number of distinct categories.
Filter-ready
Combine with search to scope results by category.
Included on the free plan — 100 requests/day, no credit card required. 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/categories?q=chocolate&limit=10" \
-H "X-API-Key: YOUR_API_KEY"
const res = await fetch("https://foodbase.dev/v1/categories?q=chocolate&limit=10", {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
import requests
res = requests.get(
"https://foodbase.dev/v1/categories?q=chocolate&limit=10",
headers={"X-API-Key": "YOUR_API_KEY"},
timeout=30,
)
res.raise_for_status()
data = res.json()
How it works
What one request returns
GET /v1/categories reads a precomputed table of every distinct category string in the catalog with how many products carry it; products with no category are excluded. Rows are sorted by product_count descending with no secondary sort, so do not rely on the order of rows with equal counts.
q is a case-insensitive substring filter, not a prefix: q=chocolate matches "Chocolate", "en:chocolates" and "Beverages, Dairies, Dairy drinks, Flavoured milks, Chocolate milks" alike, narrowing the captured response to a total of 11303 distinct strings. Whitespace is trimmed, % and _ are literal, and q is capped at 200 characters.
limit runs 1–100 (default 20) and offset 0–10000, so with limit=100 you can reach at most the first 10,100 rows; narrow with q instead of paging deeper. Responses carry Cache-Control: public, max-age=600, the server caches for an hour, and the table is rebuilt only on bulk re-import, so fetch once at startup or in a build step and store the result.
Two shapes of category string
Open Food Facts products carry a comma-joined hierarchy, general to specific: "Snacks, Sweet snacks, Cocoa and its products, Chocolates, Dark chocolates". Segments may be raw taxonomy tags with a two-letter language prefix ("en:chocolate-candies"), may be in the label's language ("Botanas, Snacks dulces, Cacao y sus productos, Chocolates, Chocolates negros"), and often end in a catch-all such as en:groceries that is not the most specific level.
USDA products carry one flat retail category whose name can itself contain commas ("Chips, Pretzels & Snacks"); never split it. Rows here carry no source, so when you need it, fetch a product from the category with the Food Database API and read source next to category and categories_tags.
One hierarchy can appear as several rows: the captured response lists "Snacks, Sweet snacks, Cocoa and its products, Chocolates, Dark chocolates" (497 products) and the same path without spaces after the commas (339) separately. Merge on cleaned segments, not on the raw string.
Building a browse tree or filter UI
Apply the cleaning the website uses: split on commas, trim, strip a leading xx: tag, replace hyphens with spaces, capitalize the first letter, and drop repeated segments case-insensitively. The first cleaned segment is the root; the last one that is not a catch-all (groceries, food, foods, products, unknown, none) is the leaf. A node's count is the sum of product_count over every raw string that maps to it.
- Root nodes: group by first cleaned segment ("Snacks", "Beverages", "Botanas").
- Leaf chips: the last non-catch-all segment ("Dark chocolates", "Chocolate milks").
- Flat USDA names: the whole string is both root and leaf.
- Language: hierarchies in different languages stay separate nodes; for per-product translations pass lang= to the Food Search API and read category_localized and category_segments_localized.
- The website's browse tree at /categories/ applies the same cleaning rules to this data (and keeps only the larger leaves); use it to check your output.
From a category to its products
Search has no category parameter. The Food Search API matches product names and brands first and, only when that finds nothing, matches the words of your query against category strings. Send a complete cleaned segment that is not itself a product name as q, such as "Seeds & Related Snacks", and you get that category's products ordered by popularity.
The fallback is word-based, so "Seeds & Related Snacks" matches every category string containing those words in any language, and a segment that is also a common product word ("Chocolate" or "Dark chocolates") never reaches it because product names match first. For a strict listing, filter each result on its category field, which is the raw string this endpoint returned.
Build with it
Filters & facets
Render category navigation with live product counts.
Catalog overviews
Show users the shape of the dataset before they search.
Analytics dashboards
Chart how products distribute across categories.
Limits by plan
| Free | 100 requests/day shared across endpoints, 1/s (burst 5); enough to refresh a cached tree a few times a day. |
|---|---|
| Starter | 5,000 requests/day, 5/s (burst 25). |
| Pro | 25,000 requests/day, 20/s (burst 100). |
| Enterprise | 100,000 requests/day, 50/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
401 — X-API-Key is missing, invalid, or revoked.
Send a key from the dashboard; the body's error field says which.
422 — limit outside 1–100, offset outside 0–10000, or q over 200 characters.
Clamp values client-side; validation returns 422 here, not 400.
429 — The key's per-second limit or the account's daily quota is exhausted.
Wait for Retry-After and serve your cached copy meanwhile.
Compared to other APIs
This is a listing of category strings as stored, with counts, not a curated taxonomy. Open Food Facts contributors and the USDA import each supply their own shape, spacing, language and tags, and the endpoint does not merge them; the cleaning rules above are yours to apply. In exchange the counts match the catalog exactly and every string can be passed straight back into search or compared against a product's category field. Build one normalized tree from this list and cache it, because the data only changes on re-import.
Frequently asked questions
Is q a prefix or a substring match? +
Substring, case-insensitive, anywhere in the string; q=chocolate returns "Beverages, Dairies, Dairy drinks, Flavoured milks, Chocolate milks" as well as "Chocolate". There is no way to anchor it to the start.
Why do I see the same hierarchy more than once? +
The stored strings differ in spacing, tagging ("en:chocolates" versus "Chocolates") or language, and the endpoint counts per raw string. Merge on cleaned segments to collapse them.
Can I list only USDA or only Open Food Facts categories? +
No: there is no source filter and rows carry no source. Fetch a product from the category with the Food Database API and read its source field.
Can I filter search by category? +
Not with a parameter. Send the segment as q to the Food Search API, which falls back to category matching only when no product name or brand matches; filter on each result's category field for an exact match.
Does total count products or categories? +
Distinct category strings matching q: in the captured response total is 11303 for q=chocolate while the first row alone holds 46110 products. Sum product_count yourself for a product total.
Explore more endpoints
Food Search API
Full-text search across 6.1 million food products by name, brand, or category — relevance-ranked, paginated, and fast. Search in any language the label was printed in (Cyrillic included), and pass lang=en|fr|es|de|it|bg for localized names, categories and allergens. One GET request returns clean JSON your app can render immediately.
Learn more →Food Database API
A single source for 6.1 million foods. Fetch a complete product record by ID — name, brand, ingredients, allergens, Nutri-Score, NOVA group, images, and more.
Learn more →Start building with the Food Categories API
Free to start. No credit card required.
Get API key Go to Dashboard