AI Food Analysis API
One call turns a product's raw data into something you can show a user: a plain-language summary, factual health highlights and warnings, diet compatibility (vegan, vegetarian, gluten-free, lactose-free), and a NOVA-style processing level — in six languages, grounded strictly in the product's label data.
curl -X POST -H "X-API-Key: food_•••" \
-H "Content-Type: application/json" \
-d '{"barcode":"8000500082379","lang":"en"}' \
"https://foodbase.dev/v1/analyze/product"
{
"analysis": {
"summary": "Nutella is a chocolate hazelnut
spread... 533 kcal per 100g.",
"highlights": ["Contains 13% hazelnuts"],
"warnings": [
"High in sugar (56.3g per 100g)",
"Contains milk and tree nuts"
],
"diets": { "vegan": "no", "vegetarian": "yes" },
"processing_level": "ultra_processed"
},
"cached": false
} Prefer to click around? Try the product explorer or the photo playground.
What you get back
summary
2–3 plain sentences describing the product and its nutritional character.
highlights / warnings
Short, factual points grounded in the label data — never invented.
diets
vegan / vegetarian / gluten_free / lactose_free — yes, no, or unknown.
processing_level
NOVA-style judgement from the ingredients list.
lang
en, fr, es, de, it, or bg — the analysis is written in the requested language.
cached
Each product+language is generated once, then served instantly. 1 credit per call.
Available on every plan through monthly AI credits — each call has a fixed credit price, and every plan (including Free) comes with a credit allowance. 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/analyze/product" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"food_id":"62ea12b8-b260-c288-5c38-3a4906f5d6dd","lang":"en"}'
const res = await fetch("https://foodbase.dev/v1/analyze/product", {
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
"food_id": "62ea12b8-b260-c288-5c38-3a4906f5d6dd",
"lang": "en"
}),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
import requests
res = requests.post(
"https://foodbase.dev/v1/analyze/product",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"food_id": "62ea12b8-b260-c288-5c38-3a4906f5d6dd",
"lang": "en"
},
timeout=30,
)
res.raise_for_status()
data = res.json()
How it works
Address the product, pick the language
POST /v1/analyze/product takes a JSON body with exactly one of food_id or barcode (8 to 14 digits; zero-padded GTIN variants resolve) plus an optional lang. Send both or neither and you get a 422 reading "Provide exactly one of food_id or barcode", with the credit refunded. Empty strings count as omitted.
lang defaults to en and also accepts fr, es, de, it and bg. It sets the language of the text, not the verdicts: the first analysis of a product is canonical, and other languages are translated from it with diets and processing_level copied verbatim. The model reads the record's name, brand, category, quantity, allergens, ingredients and nutrients row, the same data the Food Database API and the Nutrition API return. From a picture, the Food Image Recognition API gets you the food_id first.
Reading the analysis, field by field
The captured response for food 62ea12b8-b260-c288-5c38-3a4906f5d6dd (Coca-Cola, lang en) shows the shape. summary is at most 500 characters in the canonical language; highlights and warnings hold at most 8 strings each. Here warnings is "High in sugar (10.6 g/100 g)", "Contains added sugars (5.3 g/100 g)" and "Contains caffeine"; highlights includes "Low energy per 100 g (42 kcal)". Every number in those strings is quoted from the nutrients row; the prompt forbids inventing a value absent from the data.
diets has four keys, vegan, vegetarian, gluten_free and lactose_free, each exactly "yes", "no" or "unknown". "yes" and "no" appear only when ingredients or allergen data supports them; missing ingredients give "unknown", which means "do not filter on this", never "no". processing_level is one of unprocessed, minimally_processed, processed, ultra_processed or unknown, judged from the ingredients; the cola is ultra_processed. per_serving is one sentence in serving context when the record's quantity data allows it, otherwise null, as in the sample.
disclaimer is the fixed string "AI-generated from the product's label data. Informational only — not medical or dietary advice. Verify the physical label for allergens." Show it: the text is generated, and it describes the catalog record, not the pack in the user's hand.
Credits, caching and refunds
Each call costs 1 AI credit from a monthly allowance shared by all keys on the account (100 Free, 5,000 Starter, 15,000 Pro, 40,000 Enterprise; reset on the first of the month UTC). It is deducted before inference, and every response carries X-Credits-Limit, X-Credits-Remaining and X-Credits-Reset.
Analyses are cached per product, language and analysis version, tied to the record's updated_at, so a re-imported or corrected product is analyzed afresh. A hit returns cached: true, as in the sample, and is still charged 1 credit: there are no refunds for cache hits on the analyze endpoints. Cache the response yourself, keyed on food_id and lang, if you show the same product often.
Refunds happen only when nothing was produced: 404, 422, 502 (the message ends "You were not charged.") and 503. A 401 or 429 never touches credits.
Your own product data: /analyze/custom
POST /v1/analyze/custom returns the same analysis object for a product you describe yourself: a private-label item, a new launch, a label you just extracted. The body carries name (2 to 150 characters) plus optional brand, category, quantity, ingredients (up to 3,000 characters), a nutrients object of per-100 g values and lang. There are no free-text question or profile fields.
A name alone is a 422: send ingredients, at least one nutrient value, or both, because the model may not invent nutrition facts. nutrients is strict, with nineteen keys named like the catalog columns (energy_kcal, sugars_g, sodium_mg, calcium_mg and so on); an unknown key is a 422, and out-of-range values are rejected rather than clamped.
The response has input_hash instead of food_id, a SHA-256 of the canonicalized input, so identical data and lang is a cache hit, charged like any other. An optional barcode is not analyzed; it only flags the product as one you want in the catalog.
Adding a language to a record: /translate/product
POST /v1/translate/product takes food_id or barcode and a required lang, translates the product's name and ingredients text, and writes the result into the catalog as an overlay that survives re-imports and is visible to every caller as a new {lang, text} entry in the record's name and ingredients arrays, and through name_localized in search. Label data already in that language is never overwritten.
cached: true here means the language already existed and your credit is refunded. force: true regenerates a translation you consider bad, but only text from an earlier AI run; if the language's text is from the label or a manual fix, you get a 422 with the credit refunded. Low-confidence or unsafe output is never stored: the call returns 502 and you are not charged.
Build with it
Product detail screens
Show shoppers an instant, readable verdict next to the raw numbers.
Diet filters that explain themselves
Vegan or gluten-free flags with the reasoning behind them.
Health & coaching apps
Turn scans into guidance-ready text without building your own LLM pipeline.
Limits by plan
| Free | 100 AI credits/month shared across keys (100 calls at 1 credit each); 1 AI request/s per key |
|---|---|
| Starter | 5,000 AI credits/month; 1 AI request/s per key |
| Pro | 15,000 AI credits/month; 2 AI requests/s per key |
| Enterprise | 40,000 AI credits/month; 5 AI requests/s per key |
Every response carries X-RateLimit-* headers with the remaining budget;
every 429 carries Retry-After.
Compare plans →
Errors and how to handle them
402 — Monthly AI credits are exhausted; X-Credits-Remaining is 0 and the message names the reset date.
Stop AI calls until X-Credits-Reset, or upgrade. Data endpoints keep working.
404 — No product matches the food_id or barcode. Refunded.
Re-resolve by search or barcode; ids are stable, so a 404 is not transient. Outside the catalog, use /analyze/custom.
422 — Both or neither of food_id and barcode; a custom name with no ingredients or nutrients; a bad nutrient key or value; force on text that is not AI-generated. Refunded.
Read details[].field and details[].message, fix the request, do not retry unchanged.
429 — The per-key AI limit (1/s Free and Starter, 2/s Pro, 5/s Enterprise) or the account ceiling of three times that. No credit is taken.
Wait the Retry-After second and serialize AI calls per key.
502 — The model call failed, or a translation fell below the confidence bar. The message ends "You were not charged."
Retry after a short pause; a later success is cached normally.
503 — AI is not enabled, or the credit or rate-limit store is unreachable. Refunded.
Retry with backoff and show the raw record from the Food Database API meanwhile.
Compared to other APIs
A general-purpose LLM API will summarize a product if you paste the label in; you then own the prompt, the grounding rules, the output schema, caching and per-language consistency. FoodBase does that once per product: the analysis is generated from the record on file, validated against a fixed schema, cached per product and language, and translated from one canonical result so a verdict cannot differ between languages. The trade-off is that it is only as good as the record: no ingredients on file means unknown diets and processing level, and nothing is estimated to fill the gap. If you need numbers rather than prose, the Nutrition API is the cheaper call.
Frequently asked questions
Am I charged when cached is true? +
On /analyze/product and /analyze/custom, yes: a cache hit costs the same 1 credit as a fresh analysis, with no refund. On /translate/product, no: cached: true means the language already existed and the credit is refunded.
What does "unknown" in diets mean? +
The record lacks the ingredients or allergen data needed to decide; the model may answer "yes" or "no" only when that data supports it. Treat it as a statement about the record, not a soft "no".
Can I ask a custom question, such as whether a product suits a low-sodium diet? +
Not through these endpoints: the analysis has a fixed shape and /analyze/custom takes product data, not questions. Read sodium_mg or salt_g from the Nutrition API and apply your own threshold rather than parsing the warnings text.
Why is per_serving null for the Coca-Cola sample? +
per_serving is written only when the record's quantity data lets the model put the per-100 g numbers into a serving; otherwise it is null, never an estimate. Compute a serving yourself from the Nutrition API values if you need one.
What happens to the cached analysis when the product record changes? +
The cache entry is tied to the record's updated_at, so after a re-import or correction the next call generates a new analysis from the new data and charges 1 credit. Until then the cached answer is served.
Explore more endpoints
Food Image Recognition API
Turn a photo into food data. Four vision endpoints cover the whole camera flow: decode a barcode, identify a product from its front, read the nutrition label and ingredients, or analyze a plate of food with portion estimates — matched against 6.1M products, in six languages including Cyrillic.
Learn more →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.
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 AI Food Analysis API
Free to start. No credit card required.
Get API key Go to Dashboard