AI credits

AI Food Analysis API

One call turns a product's raw data into something you can show a user: a two-sentence summary, good points and watch-outs computed from the nutrition data with published thresholds, the declared allergens, diet compatibility that says where each answer came from, and the processing level — in six languages, grounded strictly in the product's label data.

200 OK
POST /v1/analyze/product
curl -X POST -H "X-API-Key: food_•••" \
  -H "Content-Type: application/json" \
  -d '{"barcode":"3017620422003","lang":"en"}' \
  "https://foodbase.dev/v1/analyze/product"

{
  "analysis": {
    "summary": "Nutella is a hazelnut and cocoa
      chocolate spread from Ferrero...",
    "highlights": ["Low salt (0.11 g per 100 g)"],
    "warnings": [
      "High in fat (30.9 g per 100 g)",
      "High in saturated fat (10.6 g per 100 g)",
      "High in sugars (56.3 g per 100 g)",
      "Contains palm oil"
    ],
    "allergens": {
      "contains": ["soy", "milk", "nuts"],
      "text": "Contains: soy, milk, nuts."
    },
    "diets_detail": {
      "vegan": { "value": "no", "basis": "allergen_tag" },
      "gluten_free": { "value": "yes", "basis": "label_claim" }
    },
    "processing_level": "ultra_processed",
    "processing_basis": "nova"
  },
  "cached": false
}

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

What you get back

summary

One or two plain sentences on what the product is and its nutritional character.

highlights / warnings

Good points (EU nutrition-claim conditions) and watch-outs (UK FSA high levels, caffeine, sweeteners, alcohol, palm oil). A topic is never in both.

allergens

EU-14 allergens the label declares, split into contains and may contain, with a localized sentence.

diets_detail

vegan / vegetarian / gluten_free / lactose_free with the basis: label claim, declared allergen, or ingredients.

nutrient_levels

Fat, saturated fat, sugars and salt as low, medium or high, per 100 g/ml or per portion.

processing_level

From the product's NOVA group; judged from the ingredients only when the record has none.

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 '{"barcode":"3017620422003","lang":"en"}'
            

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: every verdict is computed from the record by fixed rules and worded from a phrase dictionary in each language, and the model's judgements are made once per product and shared by all languages, so a product cannot be vegan in English and not in German. The rules read the record's nutrients, labels, allergen and trace tags and NOVA group, 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 sample is the live response for Nutella (barcode 3017620422003, lang en). highlights are good points only: a product earns one when it meets the condition of an EU Regulation 1924/2006 nutrition claim, such as low salt (at most 0.3 g per 100 g), source of protein (at least 12% of energy) or high fibre (at least 6 g). warnings are watch-outs only: a nutrient above the UK FSA front-of-pack "high" threshold (sugars above 22.5 g per 100 g, or above 11.25 g per 100 ml for drinks), a large portion that crosses the FSA per-portion limit, or caffeine, sweeteners, alcohol or palm oil in the ingredients. A topic is never both, and every number is the record's own value.

nutrient_levels gives the same verdicts as data: fat, saturated fat, sugars and salt, each low, medium or high, with basis per_100 or per_portion. A 330 ml cola with 10.6 g of sugars per 100 ml is medium per 100 ml but high per can, so its warning reads "High in sugars (35 g per serving)". allergens lists the declared EU-14 allergens as contains and may_contain, with status "not_stated" when the record has no allergen data, which means check the package, not "none".

diets_detail answers vegan, vegetarian, gluten_free and lactose_free with a value and a basis. A label claim (vegan, gluten-free, lactose-free, certification logos) decides first; a declared allergen or meat tag rules a diet out next; otherwise the answer comes from the ingredient list. Gluten-free and lactose-free are "yes" only with a label claim: an ingredient list with no gluten source gives "no_known_source", because an uncertified product can still be contaminated. diets keeps the older yes/no/unknown shape, with no_known_source reported as unknown.

processing_level is the NOVA group as a label — unprocessed (1), culinary_ingredient (2: oils, butter, sugar), processed (3), ultra_processed (4) — from the record's NOVA group (processing_basis "nova"); only when the record has none is it judged from the ingredients ("model"). per_serving is computed from the record's serving size, or null when none is on file. disclaimer is a fixed string; show it, because 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 4 AI credits from a monthly allowance shared by all keys on the account (20 Free, 500 Starter, 1,300 Pro, 3,000 Enterprise; reset on the first of the month UTC), then from one-time top-up credits once the allowance cannot cover a call. It is deducted before inference, and every response carries X-Credits-Limit, X-Credits-Remaining (monthly plus top-up), X-Credits-Topup-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. The response's source_updated_at names the data version it was made from; the food record's updated_at tells you whether it is still current. A hit returns cached: true and is still charged 4 credits: 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, and no label claims or allergen tags, so diets come from the ingredients and gluten-free or lactose-free is at most no_known_source.

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: /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 4 credits are 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 credits refunded. Low-confidence or unsafe output is never stored: the call returns 502 and you are not charged.

Fixing broken label text: /correct/product

Label text in the product's own language is sometimes broken at the source: OCR errors, missing spaces after commas, ALL-CAPS lists, broken characters such as "crème" for "crème", or a passage repeated twice. POST /v1/correct/product repairs it for everyone, for 4 credits. The repair may change the form only: code checks that no ingredient, number, percentage or E-number was added or removed, that commas and brackets stayed where they were, and that every allergen and its emphasis survived. A repair that fails is not stored and not charged.

When the content itself is wrong, POST /v1/correct/product/photo takes a photo of the label and uses the ingredient list read from it as evidence, for 4 credits. Reading the photo is the expensive part, so a photo that was read but cannot be used (no ingredient list visible, the barcode not readable in full, a different product, or a reading too uncertain to trust) answers 422 and is charged like /vision/label. Every correction keeps the previous text for revert, re-applies after re-imports only while the upstream source still ships the text that was fixed, and makes the AI translations of the old text regenerate on their next request. The analysis of the product regenerates too, because the record changed.

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 that say whether a label claim or the ingredient list backs them.

Health & coaching apps

Turn scans into guidance-ready text without building your own LLM pipeline.

Limits by plan

Free 20 AI credits/month shared across keys (5 analyses at 4 credits each); 1 AI request/s per key
Starter 500 AI credits/month; 1 AI request/s per key
Pro 1,300 AI credits/month; 2 AI requests/s per key
Enterprise 3,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 and top-up credits are used up; X-Credits-Remaining is 0, the message names the reset date and the body has a top_up_url.

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, except a /correct/product/photo whose photo was read but could not be used, which is charged.

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 verdicts are computed from the record on file with published thresholds (EU nutrition claims, UK FSA traffic lights), the model writes only the summary and the ingredient-based judgements, everything is validated against a fixed schema and cached per product and language, 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 nothing is estimated to fill the gap — though broken label text can be repaired with /correct/product. If you need numbers rather than prose, the Nutrition API is the cheaper call.

See the full comparison of food database APIs →

Frequently asked questions

Am I charged when cached is true? +

On /analyze/product and /analyze/custom, yes: a cache hit costs the same 4 credits as a fresh analysis, with no refund. On /translate/product, no: cached: true means the language already existed and your 4 credits are refunded.

What do "unknown" and "no_known_source" in diets mean? +

unknown: the record has neither a label claim, an allergen tag nor an ingredient list that settles it — a statement about the record, not a soft "no". no_known_source (gluten-free and lactose-free only): the ingredient list names no source, but the label makes no claim, so the product is not certified. Coeliac or lactose-intolerant users should treat only "yes" with basis label_claim as safe.

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.

Where do the good points and watch-outs come from? +

From fixed rules, not the model: good points use the conditions of the EU nutrition claims (Regulation 1924/2006), watch-outs the UK FSA front-of-pack high thresholds, including the per-portion limits for portions over 100 g or 150 ml. The same product therefore gets the same points every time and in every language, and nutrient_levels returns them as data you can filter on.

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 4 credits. Until then the cached answer is served.

Start building with the AI Food Analysis API

Free to start. No credit card required.

Get API key Go to Dashboard