openfoodfacts-mcp-server
Server Details
Barcode lookup, nutrition search, and product comparison for 3M+ crowd-sourced food products.
- Status
- Healthy
- Uptime
- 99.9% over 75 days
- Last Tested
- Transport
- Streamable HTTP · MCP 2025-11-25
- URL
- Repository
- cyanheads/openfoodfacts-mcp-server
- GitHub Stars
- 1
- Server Listing
- @cyanheads/openfoodfacts-mcp-server
TDQS
Scored across 4 tools
Each tool has a clearly distinct role: taxonomy resolution, product search, product detail retrieval, and multi-product comparison. The descriptions explicitly cross-reference each other, making the handoff from search to get_product and from taxonomy to search clear.
All tools use a consistent snake_case pattern with the off_ server prefix followed by a verb and noun: off_browse_taxonomy, off_compare_products, off_get_product, off_search_products. The convention is predictable throughout.
Four tools is well-scoped for a read-only Open Food Facts lookup server. Each tool earns its place by supporting a distinct step in the browse-search-inspect-compare workflow, with no redundant endpoints.
The surface covers the core read workflows: resolving tags, searching products, fetching full product data, and comparing up to ten products. Minor gaps exist around bulk retrieval or direct category listing, but agents can work around them using search and taxonomy tools.
Available Tools
4 toolsoff_browse_taxonomyBrowse Food Facts TaxonomyARead-onlyIdempotentInspect
Resolve a human term to the canonical Open Food Facts tag ID that off_search_products filters on. Covers categories, labels/certifications, allergens, additives, countries, NOVA groups, and Nutri-Score grades. Pass a search term to resolve against the Open Food Facts vocabulary, which holds tens of thousands of tags; omitting it returns only a small reference list for each facet except NOVA groups and Nutri-Score grades, which are complete. Most tag IDs use the "en:" prefix (e.g. "en:organic", "en:no-gluten", "en:crustaceans"); NOVA groups return bare digits "1"-"4" and Nutri-Score grades bare letters "a"-"e". Pass the id through to off_search_products exactly as returned. Category tags are frequently plural ("kombucha" resolves to "en:kombuchas"), so use the returned id rather than constructing one.
| Name | Required | Description | Default |
|---|---|---|---|
| facet | Yes | "categories" covers food categories (en:cheeses, en:breakfast-cereals). "labels" covers certifications (en:organic, en:fair-trade). "allergens" covers declared allergens (en:milk, en:gluten). "additives" covers E-numbers (en:e322). "countries" covers country-of-sale tags (en:france). "nova_groups" and "nutrition_grades" are closed vocabularies returned complete; the other five are resolved against the Open Food Facts taxonomy. | |
| limit | No | Maximum entries to return (default 20). One call returns at most 200: a larger value returns the first 200 and, when more matched, says how many did. There is no offset or page input: Open Food Facts offers no cursor for this lookup, so narrow the search term rather than paging. The tag spelling the term itself (e.g. "lentil" → en:lentils) is listed first among the live matches, so it is not the one a small limit cuts. | |
| search | No | Term to resolve. Matched case-insensitively as a substring of the tag ID, the display name, or a common synonym of either ("shellfish" resolves to en:crustaceans, "gluten free" to en:no-gluten). A single word works best ("hummus", not "hummus dip"). Omit only to see a small reference list — Open Food Facts cannot list the full vocabulary without a term, so an unfiltered call is not a view of the full facet. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | The limit that was applied — at most 200. |
| tags | No | Matching tag entries. |
| error | No | Present when the call failed. Absent on success. |
| facet | No | The facet name that was queried (echoes the input). |
| shown | No | Number of tags returned. |
| notice | No | Caveat about the answer — that the listing is a limited reference list rather than the full vocabulary, that the live vocabulary could not be reached or gave no usable answer, that nothing matched and why, or that the limit cut matches, naming how many matched and the next call: a larger limit while one would list more, otherwise a narrower term, which is the only route past the first 200 (a limit above 200 is named too). |
| truncated | No | True when more tags exist beyond the limit. |
| total_in_facet | No | Total entries in this facet. Present only for nova_groups and nutrition_grades, whose vocabularies are closed and complete. Absent for the other facets: Open Food Facts reports no match total and cannot enumerate them, so no figure would be a real one. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/openWorld, and the description still adds non-obvious behavior: the 200-entry cap with no offset or cursor, the ordering guarantee that the exact tag spelling comes first so a small limit won't cut it, and the split between closed vocabularies (NOVA, Nutri-Score) and the open taxonomy. These are operational traits an agent could not infer from annotations or schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core resolution purpose and the downstream consumer, then layered with the operational caveats. Dense and mostly waste-free, though the list of covered facets overlaps with the facet enum's own descriptions.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 3-parameter read-only lookup with an output schema and full annotation coverage, the description covers the remaining unknowns: no pagination, the 200-result cap, id format, and how to narrow instead of page. Nothing an agent needs to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents all three parameters; the description nonetheless adds meaning beyond it — the 'en:' prefix convention, bare digits '1'-'4' for NOVA and bare letters 'a'-'e' for Nutri-Score, and single-word query advice. The facet enumeration is partly restated from the schema, which keeps it from a 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Resolve a human term to the canonical Open Food Facts tag ID') and explicitly ties the output to the sibling tool that consumes it ('that off_search_products filters on'). An agent can distinguish this resolver from off_search_products/off_get_product without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use ('Pass a search term to resolve'), when to omit it and what that yields, and a hard instruction to pass the returned id to off_search_products exactly rather than constructing one. It also anticipates the failure mode ('kombucha' → 'en:kombuchas') and tells the agent to trust the returned id.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
off_compare_productsCompare Food Products Side-by-SideARead-onlyIdempotentInspect
Side-by-side nutrition and scoring comparison for 2–10 products per call by barcode; a repeated barcode is compared once, so two identical barcodes yield one row, and barcodes past the 10th distinct one are listed in unprocessed rather than fetched. Returns a normalized table of energy (kcal), fat, saturated fat, carbohydrates, sugars, salt, protein, and fiber per 100 g, or per 100 ml for liquids, plus Nutri-Score, NOVA group, and Green-Score. Open Food Facts returns one preparation state per read, so figures for the prepared product arrive in a prepared object on the row, with the same columns and basis, and never fill an as-sold field. Designed for "which of these cereals is healthiest?" or "compare these pasta brands" workflows. Missing nutrition data for any product is preserved as absent — comparisons are not imputed. A batch is not all-or-nothing: barcodes that resolve are returned even when others fail, with confirmed-missing barcodes listed in not_found and failed fetches listed separately in failed. Scores carry regional formula caveats. Data under ODbL 1.0 — cite Open Food Facts in downstream use.
| Name | Required | Description | Default |
|---|---|---|---|
| barcodes | Yes | Barcodes to compare, returned as one row each in input order; a repeated barcode is compared once, so two identical barcodes yield one row. One call compares at most 10 distinct barcodes: up to 100 are accepted, and those past the 10th distinct one are returned in unprocessed without being fetched. Example: ["3017620422003", "7622210100146"]. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | Present when the call failed. Absent on success. |
| failed | No | Barcodes whose fetch failed, with the per-barcode reason. Absent when every fetch completed. A barcode listed here is unknown, not absent from Open Food Facts — retry it with off_get_product before concluding anything about the product. |
| products | No | Comparison rows in input order — one per distinct barcode whose fetch completed, whether or not a record exists. Barcodes whose fetch failed have no row here; they appear in failed. Barcodes past the cap appear in unprocessed. |
| not_found | No | Barcodes Open Food Facts answered for, confirming no contributor record exists. Not an error — the product may exist but not yet be entered. Never used for a fetch that failed. |
| succeeded | No | Number of barcodes that resolved to a found product. |
| unprocessed | No | Distinct barcodes past the 10th, in input order — never fetched, so nothing is known about them. Absent when the batch held 10 or fewer distinct barcodes. Compare them in another off_compare_products call (2–10 barcodes), or look a single one up with off_get_product. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the read-only/idempotent annotations, the description discloses substantial behavior: duplicate barcodes collapse to one row, barcodes past the 10th distinct are listed in unprocessed rather than fetched, missing nutrition is preserved as absent rather than imputed, and failures are partitioned into not_found vs failed. It also explains the prepared-product object and regional scoring caveats, which are exactly the operational details an agent needs.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The core purpose is front-loaded and most sentences carry useful behavioral nuance. It is longer than average, but the density of genuinely relevant details (partial results, preparation state, license) keeps it from being bloated; some barcode-cap wording duplicates the schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the multi-product complexity, partial-failure semantics, prepared-state handling, and scoring caveats, the description is complete enough for correct invocation. An output schema exists, so the description need not explain return structure, and it still usefully summarizes the returned columns.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single barcodes parameter already documents deduplication, the 10-distinct cap, and unprocessed overflow. The description restates those semantics in prose but adds no syntax or constraint detail beyond the schema, so baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb+resource+scope: 'Side-by-side nutrition and scoring comparison for 2–10 products per call by barcode.' It clearly distinguishes this multi-product comparison tool from single-product siblings like off_get_product and discovery tools like off_search_products.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It names concrete workflows ('which of these cereals is healthiest?', 'compare these pasta brands') that tell an agent when this tool fits. It does not explicitly name alternatives such as off_get_product for single-product lookups or state exclusions, so it falls short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
off_get_productGet Food Product by BarcodeARead-onlyIdempotentInspect
Fetch a packaged food product by barcode (4–40 digits: EAN-13, EAN-8, UPC, and the shorter and longer codes Open Food Facts also holds) from Open Food Facts. Returns the product name, brand, quantity, ingredients (raw text and parsed list), declared allergens, trace allergens the label warns about, additives, the product-level vegan/vegetarian/palm-oil analysis, computed scores (Nutri-Score a–e, NOVA 1–4, Green-Score), nutrition per serving and per 100 g, or per 100 ml for liquids, categories, labels, packaging, origins, countries of sale, image URL, and data completeness. Open Food Facts is a crowd-sourced database — a missing field means "not yet entered by contributors," not that the attribute is absent from the actual product. Nutrition is the exception: Open Food Facts returns one preparation state per read, so a read of prepared figures (_prepared keys under nutriments) omits as-sold figures the record can still hold. Computed scores carry regional formula caveats and are indicators, not absolute rankings. Data is under ODbL 1.0 — cite Open Food Facts in downstream use.
| Name | Required | Description | Default |
|---|---|---|---|
| fields | No | Subset of fields to return. Omitting returns all standard fields. Use to reduce payload when only scores or ingredients are needed. A field that cannot be read on its own arrives with what it depends on: nutriments brings serving_size, serving_quantity, and serving_quantity_unit so per-serving figures carry their denominator, and serving_quantity and serving_quantity_unit each bring the other plus serving_size, since a quantity reads only with its unit. requested_fields echoes the full set that was fetched. | |
| barcode | Yes | Product barcode, digits only: 4–40 digits after any leading zeros. The primary key for Open Food Facts — the barcode of an off_search_products row works as is. Example: "3017620422003" (Nutella FR). |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | Present when the call failed. Absent on success. |
| barcode | No | The input barcode, echoed back unchanged. Open Food Facts can hold the record under another form of the same code (030000010402 resolves to the record stored as 0030000010402); that stored form is not reported. |
| product | No | Product data. Always present on a successful call — a barcode with no contributor record raises the not_found error instead of returning an empty result. |
| requested_fields | No | The field subset that was fetched, when the caller passed `fields` — the requested fields plus the ones they depend on, so every field that can appear in `product` is named here. Absent means all standard fields were requested. Sections outside this subset are omitted because they were not requested — not because Open Food Facts lacks the data. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/idempotent/openWorld annotations: it discloses that missing fields mean "not yet entered by contributors" rather than absent, that nutrition returns one preparation state per read so prepared reads can omit as-sold figures, that computed scores carry regional formula caveats, and that ODbL 1.0 requires attribution. These are exactly the interpretive caveats an agent needs to avoid over-claiming results.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Wait — reassessed: the definition is front-loaded correctly but the single second sentence is bloated by an exhaustive enumeration of every returned attribute (name, brand, quantity, ingredients, allergens, additives, scores, nutrition, categories, labels, packaging, origins, image, completeness). Since an output schema exists, that inventory is redundant and crowds out the genuinely useful caveats. Score reflects over-length rather than waste-free structure.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the return-value detail is unnecessary, but the description compensates with strong caveats on data provenance, preparation-state nutrition, and score reliability. The notable gap is failure behavior: nothing says what happens when a barcode is validly formatted but unknown to the database, which matters for a primary-key lookup.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters are already documented, including the field-dependency behavior (nutriments pulling serving fields, requested_fields echo). The description's only addition is naming barcode symbologies (EAN-13, EAN-8, UPC), which is marginal over the schema's pattern and example; baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Fetch a packaged food product by barcode") plus the data source (Open Food Facts) and the full barcode range accepted. The barcode-based lookup is self-evidently distinct from the browse/search/compare siblings, so an agent can route correctly without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied by the required barcode: there is no explicit "use off_search_products when you don't have a barcode" or any statement about when this tool is the wrong choice. The one routing hint given is chaining-oriented ("the barcode of an off_search_products row works as is"), which helps but is not a use-vs-alternative rule.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
off_search_productsSearch Food ProductsARead-onlyIdempotentInspect
Search Open Food Facts by full-text query, structured tag filters, or both at once. Returns a summary list with barcodes, product names, brands, Nutri-Score, NOVA group, and categories — enough for triage and selection, not full label data. Use off_get_product on the returned barcodes for complete details. A text query and tag filters combine: every word of the query must match the product name, generic name, categories, labels, or brand, and every filter provided must hold (e.g. query "dark chocolate" with labels_tag "en:organic" and countries_tag "en:france" returns organic chocolate sold in France); numeric nutrient_filters express thresholds per 100 g, or per 100 ml for liquids, such as sugars below 8 g, and combine the same way, and each row then carries the indexed values the thresholds were checked against; additives_tag is the one exception, filtering only on searches carrying neither query nor nutrient_filters. Tag filter values are canonical tag IDs (e.g. "en:organic", "en:no-gluten") — use off_browse_taxonomy to resolve human terms to tag IDs. A case variant, synonym, or singular of a tag is resolved to its canonical ID where Open Food Facts recognizes it; anything else is matched exactly. exclude_allergens and exclude_traces drop products that declare an allergen or a "may contain" trace, but a product with no allergen or trace data entered passes them, so confirm a candidate with off_get_product before relying on it. At least one search parameter is required. The two paths read different indexes: a search carrying query is answered by the text index, a snapshot that lags the live database, while a tag-only search reads the live database and is current — so a recently contributed product can be missing from a text search and present in the same search without query. Data is crowd-sourced; result count reflects contributed products, not all products in the market. Data under ODbL 1.0 — cite Open Food Facts in downstream use.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | Page number (1-based). Use with page_size to paginate results. Pages are counted at the page_size served, which is at most 50, so repeating one larger page_size pages contiguously (page 2 at page_size 80 is results 51–100). A search by tag filters alone is served through page 10 only, so at page_size 50 it reaches the first 500 matches. A search carrying query or nutrient_filters serves only the first 10000 results, so page * page_size (as served) must stay at or below 10000. A request past either bound is rejected rather than sent; narrow the filters or change sort_by to bring other products forward. | |
| query | No | Words to find. Every word must match the product name, generic name, categories, labels, or brand — ingredients and quantity are not searched — so put only words the product itself would carry. Punctuation separates words — except an underscore, an apostrophe or period inside a word, and a decimal comma inside a number — so each part of "milk-chocolate" or "salt/pepper" must match, while "l'eau" and "1,5" stay one word. Stop words of English, French, Spanish, German, and Italian ("with", "the", "de", "mit", …) are not required, and neither is a content word that is a stop word in one of them (such as Spanish "soy"), though it still ranks the results. Names are matched in the 31 languages the text index analyzes, so a product named only in French is found by its French name. At most 24 words, counted as runs of letters and digits, so each part of a hyphenated or apostrophe-joined word counts. Example: "dark chocolate 70%". Supplying it routes the search to the text index, a snapshot that lags the live Open Food Facts database; drop it to run the same tag filters against the current data. | |
| sort_by | No | Sort order, applied on every search. Each value orders newest or highest first: "unique_scans_n" surfaces the most-scanned products, "last_modified_t" and "created_t" the most recently updated and newest records, "popularity_key" the most popular. Omitting it leaves text searches relevance-ranked and tag-only searches in the default order. | |
| page_size | No | Results per page (default 20). One call returns at most 50: a larger value is served at 50, reported in page_size and page_size_capped, and page then counts pages of 50. Keep low for initial exploration; increase for comparison workflows. | |
| brands_tag | No | Brand slug (lowercased, hyphenated). Example: "nutella", "kelloggs". A brand name is slugged the way Open Food Facts slugs it ("Ben & Jerry's" → "ben-jerry-s") and then matched exactly — a partial or misspelled slug matches nothing rather than falling back to a near match, so put open-ended brand wording in query instead. | |
| labels_tag | No | Canonical label/certification tag ID, or an array of up to 10 that must all apply. Example: "en:organic", or ["en:organic", "en:fair-trade"] for products carrying both. Use off_browse_taxonomy with facet="labels". | |
| nova_group | No | Filter by NOVA food processing class. "1"=unprocessed/minimally processed, "4"=ultra-processed. Products without a NOVA score are excluded. | |
| traces_tag | No | Canonical allergen tag ID the label warns the product may contain as a trace ("may contain nuts"). Example: "en:nuts". Trace tags are allergen tags, so off_browse_taxonomy with facet="allergens" resolves them. Selects products carrying the warning; to leave them out, use exclude_traces. | |
| additives_tag | No | Canonical additive (E-number) tag ID. Example: "en:e322", "en:e330". Use off_browse_taxonomy with facet="additives". Available only on searches carrying neither query nor nutrient_filters — both route to a backend with no additives field, so combining them is rejected instead of silently returning nothing. | |
| allergens_tag | No | Canonical allergen tag ID. Example: "en:milk", "en:gluten". Use off_browse_taxonomy with facet="allergens". Selects products that declare this allergen; it cannot select allergen-free products, because a product with no allergen tags may simply have none entered yet. To leave an allergen out, use exclude_allergens. | |
| countries_tag | No | Canonical country tag ID. Example: "en:france", "en:united-states". Filters to products sold in that country. | |
| categories_tag | No | Canonical category tag ID. Example: "en:breakfast-cereals", "en:cheeses". Use off_browse_taxonomy with facet="categories" to discover valid values. | |
| exclude_traces | No | Allergen tag IDs a product's label must not warn it may contain as traces, all applied. Example: ["en:nuts"]. Values are validated like exclude_allergens. A product with no trace data entered passes, so check a candidate with off_get_product before relying on it. | |
| nutrition_grade | No | Filter by Nutri-Score grade. "a" is highest nutritional quality, "e" is lowest. Products without a score are excluded. | |
| nutrient_filters | No | Numeric constraints on nutrient values per 100 g, or per 100 ml for liquids, combined as AND with each other and with every other filter. Pair two entries on the same nutrient to express a range (e.g. sugars gte 2 and sugars lte 8). Served only by the text backend, so supplying one routes the search there even without query — it then reads the lagging text index and is subject to the 10,000-result page window, and additives_tag cannot be combined with it. The text index compares energy-kcal, fat, saturated-fat, carbohydrates, sugars as whole numbers, so every returned row is checked against these constraints: a row outside them leaves the page and counts in outside_constraints, and a nutrient the index holds no value for does not count against a row. A page can come back short or empty while later pages hold matches, and a range inside one whole number (sugars gt 3.9 and lt 4) can pass few rows of the 10,000 reachable, so add query or tag filters to narrow it. Per-serving and prepared-product values are not searchable. | |
| exclude_allergens | No | Allergen tag IDs a product must not declare, all applied. Example: ["en:nuts", "en:peanuts"]. Each value must be an allergen tag Open Food Facts recognizes — resolve it with off_browse_taxonomy facet="allergens" — and one it does not recognize is rejected rather than sent, because it would exclude nothing. A product with no allergen data entered passes an exclusion, so check a candidate with off_get_product before relying on it. | |
| ingredients_analysis_tag | No | Vegan, vegetarian, or palm-oil verdict Open Food Facts computes from the parsed ingredients. Example: "en:vegan", "en:palm-oil-free". "en:maybe-vegan" and "en:may-contain-palm-oil" mean the ingredients could not settle it, and the "-unknown" values mean no verdict could be computed. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | The page_size that was applied. |
| page | No | Current page number (1-based). |
| error | No | Present when the call failed. Absent on success. |
| shown | No | Number of products returned on this page. |
| total | No | Matching products in the database for this search. Exact unless total_is_lower_bound is true, in which case at least this many match and the real figure is unknown. On a search constraining a nutrient the text index compares as a whole number (see nutrient_filters) this is its whole-number answer, which can include rows those constraints rule out. |
| notice | No | Guidance about this result set — echoes the filters and suggests how to broaden when nothing matched, names the current page and how far the backend will actually paginate when more results exist, or, on a page that returned no rows because it lies past the last match or every row was left off, says which page to request instead. When rows outside nutrient_filters emptied the page and more rows can be reached or lie past the window, it also suggests adding query or tag filters. |
| omitted | No | Matches on this page left off because Open Food Facts stores them under a code it cannot serve (not 4–40 digits once leading zeros are stripped), so off_get_product could not look them up either. Absent when none was. total still counts them. |
| products | No | Matching products. Use barcodes with off_get_product for full label data. |
| last_page | No | Deepest page of this result set that holds products and can be requested, at the page_size served — capped at page 10 on a search by tag filters alone and by the 10000-result window on a search the text index answers. Absent when total_is_lower_bound is true — the total it would divide is the ceiling the backend stopped counting at, so no exact last page exists — and when nothing matched at all. On a search constraining a whole-number nutrient it counts pages of the whole-number answer, so a page up to it can list few products or none. |
| page_size | No | The page_size served: the one requested, or 50 when a larger one was requested. page and last_page count pages of this size. |
| truncated | No | True when more results exist beyond this page. Absent on the final page of a result set. |
| page_count | No | Products returned on this page — page_size except on the last page, or when a match stored under a code Open Food Facts cannot serve or a row outside nutrient_filters was left off. Not the total number of pages. |
| page_size_capped | No | Present only when page_size was above 50: names the page_size requested and the 50 served, and, when a further page holds matches and can be requested, the page to request next and the results it holds. |
| exclusion_coverage | No | Present only on searches carrying exclude_allergens or exclude_traces. States that products with no allergen or trace data entered pass an exclusion, so a result is not confirmed free of the excluded allergens, and names the off_get_product fields to check. |
| outside_constraints | No | Rows the text index returned on this page that fail nutrient_filters on their indexed values, compared at the 32-bit precision the index uses, and so left off. The index returns them because it compares some nutrients as whole numbers. Separate from omitted, whose rows are matches; a row both unservable and outside counts only in omitted. total, total_is_lower_bound, and last_page still count these rows, and each page keeps its place in the result set. Absent when none was. |
| text_index_snapshot | No | Present only on searches the text backend answered. States that those results come from an index snapshot that lags the live Open Food Facts database, so a recently contributed product can be missing from them while the tag-only path still returns it. On a search carrying nutrient_filters it also echoes the constraints and says they were checked against the index values each row carries as indexed_nutriments, which can differ from what off_get_product returns. When a constraint falls on a whole-number nutrient (see nutrient_filters) it also says that total, total_is_lower_bound, and last_page describe the whole-number answer and that rows outside the constraints count in outside_constraints. Absent on tag-only searches, which read the live database. |
| total_is_lower_bound | No | True when the backend stopped counting at its ceiling and total is a floor, not the match total. Only text searches can hit it; add filters to bring the result set under the ceiling and get an exact count. On a search constraining a whole-number nutrient the floor is on the whole-number answer, not on the products that meet the constraints. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/openWorld, yet the description adds non-obvious behavior the annotations cannot: text searches hit a lagging snapshot while tag-only searches hit live data, results are capped at 10,000 with page-10 limits, crowd-sourced counts do not equal market product counts, and allergen exclusions silently pass products with no allergen data entered. The ODbL attribution requirement is a further operational constraint worth disclosing.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded and dense with information — purpose, return shape, combination semantics, index caveat, licensing — and no filler sentences. It does, however, restate a fair amount of what the very rich parameter descriptions already say (nutrient_filters thresholds, additives exclusion), making the block longer than strictly necessary for a tool whose schema is fully documented.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 17-parameter search tool with an output schema, this covers everything an agent needs to call it correctly: scaffolding requirements, filter combination logic, index-lag consequences, result caps, and the trust caveat on allergen exclusions. Return values need not be described at length because an output schema exists, and the description gives just enough triage context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real meaning on top: how query words combine with tag filters (every word, every filter must hold), that query and nutrient_filters both route to the lagging text index, that additives_tag is the lone exception, and that tag values are canonical IDs resolved through off_browse_taxonomy. It also flags that case/synonym variants are canonicalized while anything else matches exactly.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource+scope ("Search Open Food Facts by full-text query, structured tag filters, or both at once") and immediately states what comes back (barcodes, names, brands, Nutri-Score, NOVA, categories). It also names the sibling off_get_product as the follow-up for full label data, so an agent can separate this from off_compare_products and off_get_product without reading a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes to alternatives: use off_get_product on returned barcodes for full details, use off_browse_taxonomy to resolve human terms to tag IDs, and use tag-only search when current data matters. It also states the hard precondition ("At least one search parameter is required"), the additive-filter exclusion, and the nutrient_filters/additives_tag incompatibility — a complete when/when-not picture.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
- Changed
off_browse_taxonomy6 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum entries to return (default 20). One call returns at most 100: a larger value, up to 200 (the most suggestions one lookup reads), returns the first 100 and, when more matched, says how many did. There is no offset or page input: Open Food Facts offers no cursor for this lookup, so narrow the search term rather than paging. The tag spelling the term itself (e.g. \"lentil\" → en:lentils) is listed first among the live matches, so it is not the one a small limit cuts."New value: +"Maximum entries to return (default 20). One call returns at most 200: a larger value returns the first 200 and, when more matched, says how many did. There is no offset or page input: Open Food Facts offers no cursor for this lookup, so narrow the search term rather than paging. The tag spelling the term itself (e.g. \"lentil\" → en:lentils) is listed first among the live matches, so it is not the one a small limit cuts." - removed
Input schema / properties / limit / maximumRemoved value: -200 - added
Input schema / properties / limit / multipleOfAdded value: +1 - changed
Input schema / properties / limit / typePrevious value: -"integer"New value: +"number" - changed
Output schema / properties / cap / descriptionPrevious value: -"The limit that was applied — at most 100."New value: +"The limit that was applied — at most 200." - changed
Output schema / properties / notice / descriptionPrevious value: -"Caveat about the answer — that the listing is a limited reference list rather than the full vocabulary, that the live vocabulary could not be reached or gave no usable answer, that nothing matched and why, or that the limit cut matches, naming how many matched and the next call: a larger limit while one would list more, otherwise a narrower term, which is the only route past the first 100 (a limit above 100 is named too)."New value: +"Caveat about the answer — that the listing is a limited reference list rather than the full vocabulary, that the live vocabulary could not be reached or gave no usable answer, that nothing matched and why, or that the limit cut matches, naming how many matched and the next call: a larger limit while one would list more, otherwise a narrower term, which is the only route past the first 200 (a limit above 200 is named too)."
- Changed
off_compare_products10 fields changed- changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, answers 2xx with a malformed envelope, or is unreachable for a barcode — reported in that barcode's failed entry rather than failing the call. `upstream_timeout`: Open Food Facts did not answer a barcode within the request deadline, or answered 408, 425, or 504 — reported in that barcode's failed entry rather than failing the call. `upstream_rejected`: Open Food Facts refuses a barcode with a 4xx other than 404, 408, 425, or 429, or with 501 Not Implemented — reported in that barcode's failed entry rather than failing the call. `rate_limited`: This server's own per-minute product budget is spent, or Open Food Facts answers 429 — reported in that barcode's failed entry rather than failing the call. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, answers 2xx with a malformed envelope, closes the connection before finishing its answer, or is unreachable for a barcode — reported in that barcode's failed entry rather than failing the call. `upstream_timeout`: Open Food Facts did not answer a barcode within the request or retry deadline, or answered 408, 425, or 504 — reported in that barcode's failed entry rather than failing the call. `upstream_rejected`: Open Food Facts refuses a barcode with a 4xx other than 404, 408, 425, or 429, or with 501 Not Implemented — reported in that barcode's failed entry rather than failing the call. `rate_limited`: This server's own per-minute product budget is spent, or Open Food Facts answers 429 — reported in that barcode's failed entry rather than failing the call. Other values are possible when a failure originates below the handler." - added
Output schema / properties / products / items / properties / carbohydrates_100gAdded value: +{ + "description": "Total carbohydrates in grams per 100 g, or per 100 ml for liquids, as sold. Absent when not entered, or when this read returned only prepared figures (see prepared).", + "type": "number" +} - changed
Output schema / properties / products / items / properties / energy_kcal_100g / descriptionPrevious value: -"Energy in kcal per 100 g, or per 100 ml for liquids. Absent when not entered."New value: +"Energy in kcal per 100 g, or per 100 ml for liquids, as sold. Absent when not entered, or when this read returned only prepared figures (see prepared)." - changed
Output schema / properties / products / items / properties / fat_100g / descriptionPrevious value: -"Total fat in grams per 100 g, or per 100 ml for liquids. Absent when not entered."New value: +"Total fat in grams per 100 g, or per 100 ml for liquids, as sold. Absent when not entered, or when this read returned only prepared figures (see prepared)." - changed
Output schema / properties / products / items / properties / fiber_100g / descriptionPrevious value: -"Dietary fiber in grams per 100 g, or per 100 ml for liquids. Often absent."New value: +"Dietary fiber in grams per 100 g, or per 100 ml for liquids, as sold. Absent when not entered, or when this read returned only prepared figures (see prepared)." - added
Output schema / properties / products / items / properties / preparedAdded value: +{ + "additionalProperties": false, + "description": "Figures for the prepared product, on the same columns and basis as the as-sold fields, read from Open Food Facts' *_prepared_100g keys and never derived or substituted for an as-sold figure. Open Food Facts returns one preparation state per read, so as-sold fields absent beside this object mean this read returned none, not that the record holds none. Absent when the read carried none of these figures.", + "properties": { + "carbohydrates_100g": { + "description": "Total carbohydrates in grams per 100 g, or per 100 ml for liquids, as prepared. Absent when this read returned no such figure.", + "type": "number" + }, + "energy_kcal_100g": { + "description": "Energy in kcal per 100 g, or per 100 ml for liquids, as prepared. Absent when this read returned no such figure.", + "type": "number" + }, + "fat_100g": { + "description": "Total fat in grams per 100 g, or per 100 ml for liquids, as prepared. Absent when this read returned no such figure.", + "type": "number" + }, + "fiber_100g": { + "description": "Dietary fiber in grams per 100 g, or per 100 ml for liquids, as prepared. Absent when this read returned no such figure.", + "type": "number" + }, + "proteins_100g": { + "description": "Protein in grams per 100 g, or per 100 ml for liquids, as prepared. Absent when this read returned no such figure.", + "type": "number" + }, + "salt_100g": { + "description": "Salt in grams per 100 g, or per 100 ml for liquids, as prepared. Absent when this read returned no such figure.", + "type": "number" + }, + "saturated_fat_100g": { + "description": "Saturated fat in grams per 100 g, or per 100 ml for liquids, as prepared. Absent when this read returned no such figure.", + "type": "number" + }, + "sugars_100g": { + "description": "Total sugars in grams per 100 g, or per 100 ml for liquids, as prepared. Absent when this read returned no such figure.", + "type": "number" + } + }, + "type": "object" +} - changed
Output schema / properties / products / items / properties / proteins_100g / descriptionPrevious value: -"Protein in grams per 100 g, or per 100 ml for liquids. Absent when not entered."New value: +"Protein in grams per 100 g, or per 100 ml for liquids, as sold. Absent when not entered, or when this read returned only prepared figures (see prepared)." - changed
Output schema / properties / products / items / properties / salt_100g / descriptionPrevious value: -"Salt in grams per 100 g, or per 100 ml for liquids. Absent when not entered."New value: +"Salt in grams per 100 g, or per 100 ml for liquids, as sold. Absent when not entered, or when this read returned only prepared figures (see prepared)." - changed
Output schema / properties / products / items / properties / saturated_fat_100g / descriptionPrevious value: -"Saturated fat in grams per 100 g, or per 100 ml for liquids. Absent when not entered."New value: +"Saturated fat in grams per 100 g, or per 100 ml for liquids, as sold. Absent when not entered, or when this read returned only prepared figures (see prepared)." - changed
Output schema / properties / products / items / properties / sugars_100g / descriptionPrevious value: -"Total sugars in grams per 100 g, or per 100 ml for liquids. Absent when not entered."New value: +"Total sugars in grams per 100 g, or per 100 ml for liquids, as sold. Absent when not entered, or when this read returned only prepared figures (see prepared)."
- Changed
off_get_product5 fields changed- changed
Input schema / properties / fields / descriptionPrevious value: -"Subset of fields to return. Omitting returns all standard fields. Use to reduce payload when only scores or ingredients are needed. A field that cannot be read on its own arrives with what it depends on: nutriments brings serving_size, serving_quantity, and serving_quantity_unit so per-serving figures carry their denominator, and serving_quantity_unit brings the quantity it describes. requested_fields echoes the full set that was fetched."New value: +"Subset of fields to return. Omitting returns all standard fields. Use to reduce payload when only scores or ingredients are needed. A field that cannot be read on its own arrives with what it depends on: nutriments brings serving_size, serving_quantity, and serving_quantity_unit so per-serving figures carry their denominator, and serving_quantity and serving_quantity_unit each bring the other plus serving_size, since a quantity reads only with its unit. requested_fields echoes the full set that was fetched." - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `not_found`: Open Food Facts answers HTTP 404 or status 0 for the barcode — no contributor has recorded it. `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, answers 2xx with a malformed envelope, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request deadline, or answered 408, 425, or 504. `upstream_rejected`: Open Food Facts refuses the request with a 4xx other than 404, 408, 425, or 429, or with 501 Not Implemented. `rate_limited`: This server's own per-minute product budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `not_found`: Open Food Facts answers HTTP 404 or status 0 for the barcode — no contributor has recorded it. `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, answers 2xx with a malformed envelope, closes the connection before finishing its answer, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request or retry deadline, or answered 408, 425, or 504. `upstream_rejected`: Open Food Facts refuses the request with a 4xx other than 404, 408, 425, or 429, or with 501 Not Implemented. `rate_limited`: This server's own per-minute product budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / product / properties / nutriments / descriptionPrevious value: -"Nutrition figures normalized to underscore keys. All values may be absent when nutrition data not yet entered."New value: +"Nutrition figures normalized to underscore keys. The named fields hold as-sold figures, and keys ending in _prepared under additional_100g and additional_serving hold prepared-product figures. Open Food Facts returns one preparation state per read, so a read of prepared figures leaves every named field absent even when the record holds as-sold figures. All values may be absent when nutrition data not yet entered." - changed
Output schema / properties / product / properties / nutriments / properties / additional_100g / descriptionPrevious value: -"Every other nutrient Open Food Facts holds per 100 g, or per 100 ml for liquids, keyed by normalized name (calcium, iron, vitamin_c, trans_fat, added_sugars, cholesterol, energy in kJ, …). Excludes the named fields above, so a nutrient appears in exactly one place. Micronutrients are in grams, so calcium 0.071 g is 71 mg."New value: +"Every other nutrient this read returned per 100 g, or per 100 ml for liquids, keyed by normalized name (calcium, iron, vitamin_c, trans_fat, added_sugars, cholesterol, energy in kJ, …). Excludes the named fields above, so a nutrient appears in exactly one place. A key ending in _prepared (energy_kcal_prepared, carbohydrates_prepared, …) is a figure for the prepared product, not the product as sold. Micronutrients are in grams, so calcium 0.071 g is 71 mg." - changed
Output schema / properties / product / properties / nutriments / properties / additional_serving / descriptionPrevious value: -"The same nutrients per serving. Also carries the per-serving figures for macros that have a named _100g field but no named per-serving one (saturated_fat, carbohydrates, fiber, proteins, salt, sodium). Percent, % vol, and unitless figures are concentrations, so they equal the per-100 figure. Check serving_size for the denominator the other figures are measured against."New value: +"The same nutrients per serving. Also carries the per-serving figures for macros that have a named _100g field but no named per-serving one (saturated_fat, carbohydrates, fiber, proteins, salt, sodium). Percent, % vol, and unitless figures are concentrations, so they equal the per-100 figure. Check serving_size for the denominator the other figures are measured against. A key ending in _prepared is a prepared-product figure that Open Food Facts scales from the per-100 figure by serving_quantity, which is often a powder's dry portion rather than the prepared serving, so it can describe a fraction of what is consumed."
- Changed
off_search_products14 fields changed- changed
Input schema / properties / nutrient_filters / descriptionPrevious value: -"Numeric constraints on nutrient values per 100 g, or per 100 ml for liquids, combined as AND with each other and with every other filter. Pair two entries on the same nutrient to express a range (e.g. sugars gte 2 and sugars lte 8). Served only by the text backend, so supplying one routes the search there even without query — it then reads the lagging text index and is subject to the 10,000-result page window, and additives_tag cannot be combined with it. Per-serving and prepared-product values are not searchable."New value: +"Numeric constraints on nutrient values per 100 g, or per 100 ml for liquids, combined as AND with each other and with every other filter. Pair two entries on the same nutrient to express a range (e.g. sugars gte 2 and sugars lte 8). Served only by the text backend, so supplying one routes the search there even without query — it then reads the lagging text index and is subject to the 10,000-result page window, and additives_tag cannot be combined with it. The text index compares energy-kcal, fat, saturated-fat, carbohydrates, sugars as whole numbers, so every returned row is checked against these constraints: a row outside them leaves the page and counts in outside_constraints, and a nutrient the index holds no value for does not count against a row. A page can come back short or empty while later pages hold matches, and a range inside one whole number (sugars gt 3.9 and lt 4) can pass few rows of the 10,000 reachable, so add query or tag filters to narrow it. Per-serving and prepared-product values are not searchable." - changed
Input schema / properties / page_size / descriptionPrevious value: -"Results per page (default 20). One call returns at most 50: a larger value, up to 10000 (no backend serves past result 10000), is served at 50, reported in page_size and page_size_capped, and page then counts pages of 50. Keep low for initial exploration; increase for comparison workflows."New value: +"Results per page (default 20). One call returns at most 50: a larger value is served at 50, reported in page_size and page_size_capped, and page then counts pages of 50. Keep low for initial exploration; increase for comparison workflows." - removed
Input schema / properties / page_size / maximumRemoved value: -10000 - added
Input schema / properties / page_size / multipleOfAdded value: +1 - changed
Input schema / properties / page_size / typePrevious value: -"integer"New value: +"number" - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_filters`: No search query or filter was provided. `unrecognized_exclusion`: An exclude_allergens or exclude_traces value is not an allergen tag the Open Food Facts vocabulary confirms, or the vocabulary could not be reached or gave no usable answer when checking it — an unrecognized exclusion would exclude nothing. `additives_filter_needs_tag_search`: additives_tag was combined with a query or nutrient_filters, which route to a backend that cannot filter by additive. `query_too_long`: query carries more than 24 words, more than the text backend can require at once. `page_out_of_range`: A search by tag filters alone asks for a page past 10, or a search the text backend serves asks for page * page_size beyond its 10000-result window, counting page_size at the 50 served when a larger one was requested. `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, reports a search-engine failure inside an HTTP 200, answers 2xx with a malformed envelope, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request deadline, or answered 408, 425, or 504. `upstream_rejected`: Open Food Facts refuses the request with a 4xx other than 408, 425, or 429, or with 501 Not Implemented — the request as formed will be refused again. `rate_limited`: This server's own per-minute search budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_filters`: No search query or filter was provided. `unrecognized_exclusion`: An exclude_allergens or exclude_traces value is not an allergen tag the Open Food Facts vocabulary confirms, or the vocabulary could not be reached or gave no usable answer when checking it — an unrecognized exclusion would exclude nothing. `additives_filter_needs_tag_search`: additives_tag was combined with a query or nutrient_filters, which route to a backend that cannot filter by additive. `query_too_long`: query carries more than 24 words, more than the text backend can require at once. `page_out_of_range`: A search by tag filters alone asks for a page past 10, or a search the text backend serves asks for page * page_size beyond its 10000-result window, counting page_size at the 50 served when a larger one was requested. `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, reports a search-engine failure inside an HTTP 200, answers 2xx with a malformed envelope, closes the connection before finishing its answer, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request or retry deadline, flagged its text search as timed out, or answered 408, 425, or 504. `upstream_rejected`: Open Food Facts refuses the request with a 4xx other than 408, 425, or 429, or with 501 Not Implemented — the request as formed will be refused again. `rate_limited`: This server's own per-minute search budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / last_page / descriptionPrevious value: -"Deepest page of this result set that holds products and can be requested, at the page_size served — capped at page 10 on a search by tag filters alone and by the 10000-result window on a search the text index answers. Absent when total_is_lower_bound is true — the total it would divide is the ceiling the backend stopped counting at, so no exact last page exists — and when nothing matched at all."New value: +"Deepest page of this result set that holds products and can be requested, at the page_size served — capped at page 10 on a search by tag filters alone and by the 10000-result window on a search the text index answers. Absent when total_is_lower_bound is true — the total it would divide is the ceiling the backend stopped counting at, so no exact last page exists — and when nothing matched at all. On a search constraining a whole-number nutrient it counts pages of the whole-number answer, so a page up to it can list few products or none." - changed
Output schema / properties / notice / descriptionPrevious value: -"Guidance about this result set — echoes the filters and suggests how to broaden when nothing matched, names the current page and how far the backend will actually paginate when more results exist, or, on a page that returned no rows because it lies past the last match or every row was left off, says which page to request instead."New value: +"Guidance about this result set — echoes the filters and suggests how to broaden when nothing matched, names the current page and how far the backend will actually paginate when more results exist, or, on a page that returned no rows because it lies past the last match or every row was left off, says which page to request instead. When rows outside nutrient_filters emptied the page and more rows can be reached or lie past the window, it also suggests adding query or tag filters." - added
Output schema / properties / outside_constraintsAdded value: +{ + "description": "Rows the text index returned on this page that fail nutrient_filters on their indexed values, compared at the 32-bit precision the index uses, and so left off. The index returns them because it compares some nutrients as whole numbers. Separate from omitted, whose rows are matches; a row both unservable and outside counts only in omitted. total, total_is_lower_bound, and last_page still count these rows, and each page keeps its place in the result set. Absent when none was.", + "type": "number" +} - changed
Output schema / properties / page_count / descriptionPrevious value: -"Products returned on this page — page_size except on the last page, or when a match stored under a code Open Food Facts cannot serve was left off. Not the total number of pages."New value: +"Products returned on this page — page_size except on the last page, or when a match stored under a code Open Food Facts cannot serve or a row outside nutrient_filters was left off. Not the total number of pages." - changed
Output schema / properties / products / items / properties / indexed_nutriments / descriptionPrevious value: -"Present on every row of a search carrying nutrient_filters, and only there: the values the text index holds for the constrained nutrients, per 100 g, or per 100 ml for liquids — the values the filters were checked against. Keyed as off_get_product keys them (sugars_100g, energy_kcal_100g). A nutrient the index holds no value for is left out, never zero-filled, so the object can be empty. off_get_product returns the current record, whose values can differ."New value: +"Present on every row of a search carrying nutrient_filters, and only there: the values the text index holds for the constrained nutrients, per 100 g, or per 100 ml for liquids — the values the filters were checked against. Keyed as off_get_product keys them (sugars_100g, energy_kcal_100g). A nutrient the index holds no value for is left out, never zero-filled, so the object can be empty, and it does not count against the row. off_get_product returns the current record, whose values can differ." - changed
Output schema / properties / text_index_snapshot / descriptionPrevious value: -"Present only on searches the text backend answered. States that those results come from an index snapshot that lags the live Open Food Facts database, so a recently contributed product can be missing from them while the tag-only path still returns it. On a search carrying nutrient_filters it also echoes the constraints and says they were checked against the index values each row carries as indexed_nutriments, which can differ from what off_get_product returns. Absent on tag-only searches, which read the live database."New value: +"Present only on searches the text backend answered. States that those results come from an index snapshot that lags the live Open Food Facts database, so a recently contributed product can be missing from them while the tag-only path still returns it. On a search carrying nutrient_filters it also echoes the constraints and says they were checked against the index values each row carries as indexed_nutriments, which can differ from what off_get_product returns. When a constraint falls on a whole-number nutrient (see nutrient_filters) it also says that total, total_is_lower_bound, and last_page describe the whole-number answer and that rows outside the constraints count in outside_constraints. Absent on tag-only searches, which read the live database." - changed
Output schema / properties / total / descriptionPrevious value: -"Matching products in the database for this search. Exact unless total_is_lower_bound is true, in which case at least this many match and the real figure is unknown."New value: +"Matching products in the database for this search. Exact unless total_is_lower_bound is true, in which case at least this many match and the real figure is unknown. On a search constraining a nutrient the text index compares as a whole number (see nutrient_filters) this is its whole-number answer, which can include rows those constraints rule out." - changed
Output schema / properties / total_is_lower_bound / descriptionPrevious value: -"True when the backend stopped counting at its ceiling and total is a floor, not the match total. Only text searches can hit it; add filters to bring the result set under the ceiling and get an exact count."New value: +"True when the backend stopped counting at its ceiling and total is a floor, not the match total. Only text searches can hit it; add filters to bring the result set under the ceiling and get an exact count. On a search constraining a whole-number nutrient the floor is on the whole-number answer, not on the products that meet the constraints."
4 tool updates
- Changed
off_browse_taxonomy4 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum entries to return (1–100, default 20). There is no offset or page input: Open Food Facts offers no cursor for this lookup, so narrow the search term rather than paging. The tag spelling the term itself (e.g. \"lentil\" → en:lentils) is listed first among the live matches, so it is not the one a small limit cuts."New value: +"Maximum entries to return (default 20). One call returns at most 100: a larger value, up to 200 (the most suggestions one lookup reads), returns the first 100 and, when more matched, says how many did. There is no offset or page input: Open Food Facts offers no cursor for this lookup, so narrow the search term rather than paging. The tag spelling the term itself (e.g. \"lentil\" → en:lentils) is listed first among the live matches, so it is not the one a small limit cuts." - changed
Input schema / properties / limit / maximumPrevious value: -100New value: +200 - changed
Output schema / properties / cap / descriptionPrevious value: -"The limit that was applied."New value: +"The limit that was applied — at most 100." - changed
Output schema / properties / notice / descriptionPrevious value: -"Caveat about the answer — that the listing is a limited reference list rather than the full vocabulary, that Open Food Facts was unreachable, or that nothing matched and why."New value: +"Caveat about the answer — that the listing is a limited reference list rather than the full vocabulary, that the live vocabulary could not be reached or gave no usable answer, that nothing matched and why, or that the limit cut matches, naming how many matched and the next call: a larger limit while one would list more, otherwise a narrower term, which is the only route past the first 100 (a limit above 100 is named too)."
- Changed
off_compare_products12 fields changed- changed
Input schema / properties / barcodes / descriptionPrevious value: -"2–10 barcodes to compare, returned as one row each in input order. Example: [\"3017620422003\", \"7622210100146\"]."New value: +"Barcodes to compare, returned as one row each in input order; a repeated barcode is compared once, so two identical barcodes yield one row. One call compares at most 10 distinct barcodes: up to 100 are accepted, and those past the 10th distinct one are returned in unprocessed without being fetched. Example: [\"3017620422003\", \"7622210100146\"]." - changed
Input schema / properties / barcodes / maxItemsPrevious value: -10New value: +100 - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, or is unreachable for a barcode — reported in that barcode's failed entry rather than failing the call. `upstream_timeout`: Open Food Facts did not answer a barcode within the request deadline, or answered 408, 425, or 504 — reported in that barcode's failed entry rather than failing the call. `upstream_rejected`: Open Food Facts refuses a barcode with a 4xx other than 404, 408, 425, or 429, or with 501 Not Implemented — reported in that barcode's failed entry rather than failing the call. `rate_limited`: This server's own per-minute product budget is spent, or Open Food Facts answers 429 — reported in that barcode's failed entry rather than failing the call. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, answers 2xx with a malformed envelope, or is unreachable for a barcode — reported in that barcode's failed entry rather than failing the call. `upstream_timeout`: Open Food Facts did not answer a barcode within the request deadline, or answered 408, 425, or 504 — reported in that barcode's failed entry rather than failing the call. `upstream_rejected`: Open Food Facts refuses a barcode with a 4xx other than 404, 408, 425, or 429, or with 501 Not Implemented — reported in that barcode's failed entry rather than failing the call. `rate_limited`: This server's own per-minute product budget is spent, or Open Food Facts answers 429 — reported in that barcode's failed entry rather than failing the call. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / products / descriptionPrevious value: -"Comparison rows in input order — one per barcode whose fetch completed, whether or not a record exists. Barcodes whose fetch failed have no row here; they appear in failed."New value: +"Comparison rows in input order — one per distinct barcode whose fetch completed, whether or not a record exists. Barcodes whose fetch failed have no row here; they appear in failed. Barcodes past the cap appear in unprocessed." - changed
Output schema / properties / products / items / properties / energy_kcal_100g / descriptionPrevious value: -"Energy per 100g in kcal. Absent when not entered."New value: +"Energy in kcal per 100 g, or per 100 ml for liquids. Absent when not entered." - changed
Output schema / properties / products / items / properties / fat_100g / descriptionPrevious value: -"Total fat per 100g in grams. Absent when not entered."New value: +"Total fat in grams per 100 g, or per 100 ml for liquids. Absent when not entered." - changed
Output schema / properties / products / items / properties / fiber_100g / descriptionPrevious value: -"Dietary fiber per 100g in grams. Often absent."New value: +"Dietary fiber in grams per 100 g, or per 100 ml for liquids. Often absent." - changed
Output schema / properties / products / items / properties / proteins_100g / descriptionPrevious value: -"Protein per 100g in grams. Absent when not entered."New value: +"Protein in grams per 100 g, or per 100 ml for liquids. Absent when not entered." - changed
Output schema / properties / products / items / properties / salt_100g / descriptionPrevious value: -"Salt per 100g in grams. Absent when not entered."New value: +"Salt in grams per 100 g, or per 100 ml for liquids. Absent when not entered." - changed
Output schema / properties / products / items / properties / saturated_fat_100g / descriptionPrevious value: -"Saturated fat per 100g in grams. Absent when not entered."New value: +"Saturated fat in grams per 100 g, or per 100 ml for liquids. Absent when not entered." - changed
Output schema / properties / products / items / properties / sugars_100g / descriptionPrevious value: -"Total sugars per 100g in grams. Absent when not entered."New value: +"Total sugars in grams per 100 g, or per 100 ml for liquids. Absent when not entered." - added
Output schema / properties / unprocessedAdded value: +{ + "description": "Distinct barcodes past the 10th, in input order — never fetched, so nothing is known about them. Absent when the batch held 10 or fewer distinct barcodes. Compare them in another off_compare_products call (2–10 barcodes), or look a single one up with off_get_product.", + "items": { + "description": "Barcode left unprocessed, as provided in input.", + "type": "string" + }, + "type": "array" +}
- Changed
off_get_product17 fields changed- changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `not_found`: Open Food Facts answers HTTP 404 or status 0 for the barcode — no contributor has recorded it. `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request deadline, or answered 408, 425, or 504. `upstream_rejected`: Open Food Facts refuses the request with a 4xx other than 404, 408, 425, or 429, or with 501 Not Implemented. `rate_limited`: This server's own per-minute product budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `not_found`: Open Food Facts answers HTTP 404 or status 0 for the barcode — no contributor has recorded it. `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, answers 2xx with a malformed envelope, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request deadline, or answered 408, 425, or 504. `upstream_rejected`: Open Food Facts refuses the request with a 4xx other than 404, 408, 425, or 429, or with 501 Not Implemented. `rate_limited`: This server's own per-minute product budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / product / properties / nutriments / properties / additional_100g / additionalProperties / properties / unit / descriptionPrevious value: -"Unit the figure is expressed in (\"g\", \"kcal\", \"kJ\"). Absent when Open Food Facts records no unit for this nutrient."New value: +"Unit Open Food Facts normalizes this nutrient to: \"g\" for most nutrients, \"kJ\" for energy, energy_kj, and energy_from_fat, \"kcal\" for energy_kcal, \"% vol\" for alcohol, \"%\" for the fruits-vegetables, collagen, cocoa, whole-grain, moisture, and crude (ash, fat, fibre, protein) ratios, and \"mmol/l\" for water hardness; a _prepared entry takes its base nutrient's unit. Absent for the unitless pH and glycemic index." - changed
Output schema / properties / product / properties / nutriments / properties / additional_100g / additionalProperties / properties / value / descriptionPrevious value: -"The figure Open Food Facts reported per 100g."New value: +"The figure Open Food Facts holds per 100 g, or per 100 ml for liquids." - changed
Output schema / properties / product / properties / nutriments / properties / additional_100g / descriptionPrevious value: -"Every other per-100g nutrient Open Food Facts holds, keyed by normalized name (calcium, iron, vitamin_c, trans_fat, added_sugars, cholesterol, energy in kJ, …). Excludes the named fields above, so a nutrient appears in exactly one place. Micronutrients are usually reported in grams, so calcium 0.071 g is 71 mg — read the unit rather than assuming."New value: +"Every other nutrient Open Food Facts holds per 100 g, or per 100 ml for liquids, keyed by normalized name (calcium, iron, vitamin_c, trans_fat, added_sugars, cholesterol, energy in kJ, …). Excludes the named fields above, so a nutrient appears in exactly one place. Micronutrients are in grams, so calcium 0.071 g is 71 mg." - changed
Output schema / properties / product / properties / nutriments / properties / additional_serving / additionalProperties / properties / unit / descriptionPrevious value: -"Unit the figure is expressed in (\"g\", \"kcal\", \"kJ\"). Absent when Open Food Facts records no unit for this nutrient."New value: +"Unit Open Food Facts normalizes this nutrient to: \"g\" for most nutrients, \"kJ\" for energy, energy_kj, and energy_from_fat, \"kcal\" for energy_kcal, \"% vol\" for alcohol, \"%\" for the fruits-vegetables, collagen, cocoa, whole-grain, moisture, and crude (ash, fat, fibre, protein) ratios, and \"mmol/l\" for water hardness; a _prepared entry takes its base nutrient's unit. Absent for the unitless pH and glycemic index." - changed
Output schema / properties / product / properties / nutriments / properties / additional_serving / additionalProperties / properties / value / descriptionPrevious value: -"The figure Open Food Facts reported per serving."New value: +"The figure Open Food Facts holds per serving." - changed
Output schema / properties / product / properties / nutriments / properties / additional_serving / descriptionPrevious value: -"The same nutrients per serving. Also carries the per-serving figures for macros that have a named per-100g field but no named per-serving one (saturated_fat, carbohydrates, fiber, proteins, salt, sodium). Check serving_size for the denominator these figures are measured against."New value: +"The same nutrients per serving. Also carries the per-serving figures for macros that have a named _100g field but no named per-serving one (saturated_fat, carbohydrates, fiber, proteins, salt, sodium). Percent, % vol, and unitless figures are concentrations, so they equal the per-100 figure. Check serving_size for the denominator the other figures are measured against." - changed
Output schema / properties / product / properties / nutriments / properties / carbohydrates_100g / descriptionPrevious value: -"Total carbohydrates per 100g in grams."New value: +"Total carbohydrates in grams per 100 g, or per 100 ml for liquids." - changed
Output schema / properties / product / properties / nutriments / properties / energy_kcal_100g / descriptionPrevious value: -"Energy per 100g in kcal."New value: +"Energy in kcal per 100 g, or per 100 ml for liquids." - changed
Output schema / properties / product / properties / nutriments / properties / fat_100g / descriptionPrevious value: -"Total fat per 100g in grams."New value: +"Total fat in grams per 100 g, or per 100 ml for liquids." - changed
Output schema / properties / product / properties / nutriments / properties / fiber_100g / descriptionPrevious value: -"Dietary fiber per 100g in grams. Often absent."New value: +"Dietary fiber in grams per 100 g, or per 100 ml for liquids. Often absent." - changed
Output schema / properties / product / properties / nutriments / properties / proteins_100g / descriptionPrevious value: -"Protein per 100g in grams."New value: +"Protein in grams per 100 g, or per 100 ml for liquids." - changed
Output schema / properties / product / properties / nutriments / properties / salt_100g / descriptionPrevious value: -"Salt per 100g in grams."New value: +"Salt in grams per 100 g, or per 100 ml for liquids." - changed
Output schema / properties / product / properties / nutriments / properties / saturated_fat_100g / descriptionPrevious value: -"Saturated fat per 100g in grams."New value: +"Saturated fat in grams per 100 g, or per 100 ml for liquids." - changed
Output schema / properties / product / properties / nutriments / properties / sodium_100g / descriptionPrevious value: -"Sodium per 100g in grams."New value: +"Sodium in grams per 100 g, or per 100 ml for liquids." - changed
Output schema / properties / product / properties / nutriments / properties / sugars_100g / descriptionPrevious value: -"Total sugars per 100g in grams."New value: +"Total sugars in grams per 100 g, or per 100 ml for liquids." - changed
Output schema / properties / product / properties / serving_size / descriptionPrevious value: -"Serving size as printed on the label (e.g. \"28 g\", \"1 can (12 fl oz)\"). The denominator for every per-serving figure. Absent when contributors have not entered one, in which case per-serving values cannot be converted to or from the per-100g values."New value: +"Serving size as printed on the label (e.g. \"28 g\", \"1 can (12 fl oz)\"). The denominator for every per-serving figure. Absent when contributors have not entered one, in which case per-serving values cannot be converted to or from the per-100 values."
- Changed
off_search_products17 fields changed- changed
Input schema / properties / nutrient_filters / descriptionPrevious value: -"Numeric constraints on nutrient values per 100 g, combined as AND with each other and with every other filter. Pair two entries on the same nutrient to express a range (e.g. sugars gte 2 and sugars lte 8). Served only by the text backend, so supplying one routes the search there even without query — it then reads the lagging text index and is subject to the 10,000-result page window, and additives_tag cannot be combined with it. Per-serving and prepared-product values are not searchable."New value: +"Numeric constraints on nutrient values per 100 g, or per 100 ml for liquids, combined as AND with each other and with every other filter. Pair two entries on the same nutrient to express a range (e.g. sugars gte 2 and sugars lte 8). Served only by the text backend, so supplying one routes the search there even without query — it then reads the lagging text index and is subject to the 10,000-result page window, and additives_tag cannot be combined with it. Per-serving and prepared-product values are not searchable." - changed
Input schema / properties / nutrient_filters / items / descriptionPrevious value: -"One numeric constraint on a per-100 g nutrient value."New value: +"One numeric constraint on a nutrient value per 100 g, or per 100 ml for liquids." - changed
Input schema / properties / nutrient_filters / items / properties / nutrient / descriptionPrevious value: -"Nutrient to constrain, measured per 100 g. Energy is kilocalories; every other value is grams per 100 g."New value: +"Nutrient to constrain, measured per 100 g, or per 100 ml for liquids. Energy is kilocalories; every other value is grams." - changed
Input schema / properties / nutrient_filters / items / properties / value / descriptionPrevious value: -"Threshold to compare against, in the nutrient's per-100 g unit."New value: +"Threshold to compare against: kilocalories for energy, grams for every other nutrient, per 100 g, or per 100 ml for liquids." - changed
Input schema / properties / page / descriptionPrevious value: -"Page number (1-based). Use with page_size to paginate results. A search by tag filters alone is served through page 10 only, so at page_size 50 it reaches the first 500 matches. A search carrying query or nutrient_filters serves only the first 10000 results, so page * page_size must stay at or below 10000. A request past either bound is rejected rather than sent; narrow the filters or change sort_by to bring other products forward."New value: +"Page number (1-based). Use with page_size to paginate results. Pages are counted at the page_size served, which is at most 50, so repeating one larger page_size pages contiguously (page 2 at page_size 80 is results 51–100). A search by tag filters alone is served through page 10 only, so at page_size 50 it reaches the first 500 matches. A search carrying query or nutrient_filters serves only the first 10000 results, so page * page_size (as served) must stay at or below 10000. A request past either bound is rejected rather than sent; narrow the filters or change sort_by to bring other products forward." - changed
Input schema / properties / page_size / descriptionPrevious value: -"Results per page (1–50, default 20). Keep low for initial exploration; increase for comparison workflows."New value: +"Results per page (default 20). One call returns at most 50: a larger value, up to 10000 (no backend serves past result 10000), is served at 50, reported in page_size and page_size_capped, and page then counts pages of 50. Keep low for initial exploration; increase for comparison workflows." - changed
Input schema / properties / page_size / maximumPrevious value: -50New value: +10000 - changed
Input schema / properties / query / descriptionPrevious value: -"Words to find. Every word must match the product name, generic name, categories, labels, or brand — ingredients and quantity are not searched — so put only words the product itself would carry. Stop words of English, French, Spanish, German, and Italian (\"with\", \"the\", \"de\", \"mit\", …) are not required, and neither is a content word that is a stop word in one of them (such as Spanish \"soy\"), though it still ranks the results. Names are matched in the 31 languages the text index analyzes, so a product named only in French is found by its French name. At most 24 words, counting each part of a hyphenated word. Example: \"dark chocolate 70%\". Supplying it routes the search to the text index, a snapshot that lags the live Open Food Facts database; drop it to run the same tag filters against the current data."New value: +"Words to find. Every word must match the product name, generic name, categories, labels, or brand — ingredients and quantity are not searched — so put only words the product itself would carry. Punctuation separates words — except an underscore, an apostrophe or period inside a word, and a decimal comma inside a number — so each part of \"milk-chocolate\" or \"salt/pepper\" must match, while \"l'eau\" and \"1,5\" stay one word. Stop words of English, French, Spanish, German, and Italian (\"with\", \"the\", \"de\", \"mit\", …) are not required, and neither is a content word that is a stop word in one of them (such as Spanish \"soy\"), though it still ranks the results. Names are matched in the 31 languages the text index analyzes, so a product named only in French is found by its French name. At most 24 words, counted as runs of letters and digits, so each part of a hyphenated or apostrophe-joined word counts. Example: \"dark chocolate 70%\". Supplying it routes the search to the text index, a snapshot that lags the live Open Food Facts database; drop it to run the same tag filters against the current data." - changed
Output schema / anyOfPrevious value: -[ - { - "not": { - "required": [ - "error" - ] - }, - "required": [ - "total", - "total_is_lower_bound", - "page", - "page_count", - "products" - ] - }, - { - "required": [ - "error" - ] - } -]New value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "total", + "total_is_lower_bound", + "page", + "page_size", + "page_count", + "products" + ] + }, + { + "required": [ + "error" + ] + } +] - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_filters`: No search query or filter was provided. `unrecognized_exclusion`: An exclude_allergens or exclude_traces value is not an allergen tag the Open Food Facts vocabulary confirms, or the vocabulary could not be reached to check it — an unrecognized exclusion would exclude nothing. `additives_filter_needs_tag_search`: additives_tag was combined with a query or nutrient_filters, which route to a backend that cannot filter by additive. `query_too_long`: query carries more than 24 words, more than the text backend can require at once. `page_out_of_range`: A search by tag filters alone asks for a page past 10, or a search the text backend serves asks for page * page_size beyond its 10000-result window. `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, reports a search-engine failure inside an HTTP 200, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request deadline, or answered 408, 425, or 504. `upstream_rejected`: Open Food Facts refuses the request with a 4xx other than 408, 425, or 429, or with 501 Not Implemented — the request as formed will be refused again. `rate_limited`: This server's own per-minute search budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_filters`: No search query or filter was provided. `unrecognized_exclusion`: An exclude_allergens or exclude_traces value is not an allergen tag the Open Food Facts vocabulary confirms, or the vocabulary could not be reached or gave no usable answer when checking it — an unrecognized exclusion would exclude nothing. `additives_filter_needs_tag_search`: additives_tag was combined with a query or nutrient_filters, which route to a backend that cannot filter by additive. `query_too_long`: query carries more than 24 words, more than the text backend can require at once. `page_out_of_range`: A search by tag filters alone asks for a page past 10, or a search the text backend serves asks for page * page_size beyond its 10000-result window, counting page_size at the 50 served when a larger one was requested. `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, reports a search-engine failure inside an HTTP 200, answers 2xx with a malformed envelope, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request deadline, or answered 408, 425, or 504. `upstream_rejected`: Open Food Facts refuses the request with a 4xx other than 408, 425, or 429, or with 501 Not Implemented — the request as formed will be refused again. `rate_limited`: This server's own per-minute search budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / last_page / descriptionPrevious value: -"Deepest page of this result set that holds products and can be requested, at the page_size used — capped at page 10 on a search by tag filters alone and by the 10000-result window on a search the text index answers. Absent when total_is_lower_bound is true — the total it would divide is the ceiling the backend stopped counting at, so no exact last page exists — and when nothing matched at all."New value: +"Deepest page of this result set that holds products and can be requested, at the page_size served — capped at page 10 on a search by tag filters alone and by the 10000-result window on a search the text index answers. Absent when total_is_lower_bound is true — the total it would divide is the ceiling the backend stopped counting at, so no exact last page exists — and when nothing matched at all." - changed
Output schema / properties / notice / descriptionPrevious value: -"Guidance about this result set — echoes the filters and suggests how to broaden when nothing matched, or names the current page and how far the backend will actually paginate when more results exist."New value: +"Guidance about this result set — echoes the filters and suggests how to broaden when nothing matched, names the current page and how far the backend will actually paginate when more results exist, or, on a page that returned no rows because it lies past the last match or every row was left off, says which page to request instead." - added
Output schema / properties / page_sizeAdded value: +{ + "description": "The page_size served: the one requested, or 50 when a larger one was requested. page and last_page count pages of this size.", + "type": "number" +} - added
Output schema / properties / page_size_cappedAdded value: +{ + "description": "Present only when page_size was above 50: names the page_size requested and the 50 served, and, when a further page holds matches and can be requested, the page to request next and the results it holds.", + "type": "string" +} - added
Output schema / properties / products / items / properties / indexed_nutrimentsAdded value: +{ + "additionalProperties": false, + "description": "Present on every row of a search carrying nutrient_filters, and only there: the values the text index holds for the constrained nutrients, per 100 g, or per 100 ml for liquids — the values the filters were checked against. Keyed as off_get_product keys them (sugars_100g, energy_kcal_100g). A nutrient the index holds no value for is left out, never zero-filled, so the object can be empty. off_get_product returns the current record, whose values can differ.", + "properties": { + "carbohydrates_100g": { + "description": "Carbohydrates in g per 100 g, or per 100 ml for liquids, as the text index holds it. Absent when this nutrient was not constrained or the index holds no value for it.", + "type": "number" + }, + "energy_kcal_100g": { + "description": "Energy in kcal per 100 g, or per 100 ml for liquids, as the text index holds it. Absent when this nutrient was not constrained or the index holds no value for it.", + "type": "number" + }, + "fat_100g": { + "description": "Fat in g per 100 g, or per 100 ml for liquids, as the text index holds it. Absent when this nutrient was not constrained or the index holds no value for it.", + "type": "number" + }, + "fiber_100g": { + "description": "Fiber in g per 100 g, or per 100 ml for liquids, as the text index holds it. Absent when this nutrient was not constrained or the index holds no value for it.", + "type": "number" + }, + "proteins_100g": { + "description": "Proteins in g per 100 g, or per 100 ml for liquids, as the text index holds it. Absent when this nutrient was not constrained or the index holds no value for it.", + "type": "number" + }, + "salt_100g": { + "description": "Salt in g per 100 g, or per 100 ml for liquids, as the text index holds it. Absent when this nutrient was not constrained or the index holds no value for it.", + "type": "number" + }, + "saturated_fat_100g": { + "description": "Saturated fat in g per 100 g, or per 100 ml for liquids, as the text index holds it. Absent when this nutrient was not constrained or the index holds no value for it.", + "type": "number" + }, + "sodium_100g": { + "description": "Sodium in g per 100 g, or per 100 ml for liquids, as the text index holds it. Absent when this nutrient was not constrained or the index holds no value for it.", + "type": "number" + }, + "sugars_100g": { + "description": "Sugars in g per 100 g, or per 100 ml for liquids, as the text index holds it. Absent when this nutrient was not constrained or the index holds no value for it.", + "type": "number" + } + }, + "type": "object" +} - changed
Output schema / properties / text_index_snapshot / descriptionPrevious value: -"Present only on searches the text backend answered. States that those results come from an index snapshot that lags the live Open Food Facts database, so a recently contributed product can be missing from them while the tag-only path still returns it. Absent on tag-only searches, which read the live database."New value: +"Present only on searches the text backend answered. States that those results come from an index snapshot that lags the live Open Food Facts database, so a recently contributed product can be missing from them while the tag-only path still returns it. On a search carrying nutrient_filters it also echoes the constraints and says they were checked against the index values each row carries as indexed_nutriments, which can differ from what off_get_product returns. Absent on tag-only searches, which read the live database." - changed
Output schema / properties / truncated / descriptionPrevious value: -"True when more results exist beyond this page."New value: +"True when more results exist beyond this page. Absent on the final page of a result set."
3 tool updates
- Changed
off_compare_products1 field changed- changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `upstream_error`: Open Food Facts returns a 5xx other than 501, serves an HTML error page with a 2xx or 5xx status, or is unreachable — surfaced per barcode in failed[]. `upstream_timeout`: Open Food Facts did not answer within the request deadline — surfaced per barcode in failed[]. `upstream_rejected`: Open Food Facts answers 4xx or 501 Not Implemented for a barcode — surfaced per barcode in failed[]. `rate_limited`: This server's own per-minute request budget is spent, or Open Food Facts answers 429 — surfaced per barcode in failed[]. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, or is unreachable for a barcode — reported in that barcode's failed entry rather than failing the call. `upstream_timeout`: Open Food Facts did not answer a barcode within the request deadline, or answered 408, 425, or 504 — reported in that barcode's failed entry rather than failing the call. `upstream_rejected`: Open Food Facts refuses a barcode with a 4xx other than 404, 408, 425, or 429, or with 501 Not Implemented — reported in that barcode's failed entry rather than failing the call. `rate_limited`: This server's own per-minute product budget is spent, or Open Food Facts answers 429 — reported in that barcode's failed entry rather than failing the call. Other values are possible when a failure originates below the handler."
- Changed
off_get_product1 field changed- changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `not_found`: Barcode status:0 — not present in any contributor record. `upstream_error`: Open Food Facts returns a 5xx other than 501, serves an HTML error page with a 2xx or 5xx status, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request deadline. `upstream_rejected`: Open Food Facts answers 4xx for something other than a missing barcode, or 501 Not Implemented. `rate_limited`: This server's own per-minute request budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `not_found`: Open Food Facts answers HTTP 404 or status 0 for the barcode — no contributor has recorded it. `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request deadline, or answered 408, 425, or 504. `upstream_rejected`: Open Food Facts refuses the request with a 4xx other than 404, 408, 425, or 429, or with 501 Not Implemented. `rate_limited`: This server's own per-minute product budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler."
- Changed
off_search_products1 field changed- changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_filters`: No search query or filter was provided. `unrecognized_exclusion`: An exclude_allergens or exclude_traces value is not an allergen tag the Open Food Facts vocabulary confirms, or the vocabulary could not be reached to check it — an unrecognized exclusion would exclude nothing. `additives_filter_needs_tag_search`: additives_tag was combined with a query or nutrient_filters, which route to a backend that cannot filter by additive. `query_too_long`: query carries more than 24 words, more than the text backend can require at once. `page_out_of_range`: A search by tag filters alone asks for a page past 10, or a search the text backend serves asks for page * page_size beyond its 10000-result window. `upstream_error`: Open Food Facts returns a 5xx other than 501, serves an HTML error page with a 2xx or 5xx status, reports a search-engine failure inside an HTTP 200, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request deadline. `upstream_rejected`: Open Food Facts answers 4xx or 501 Not Implemented — the request as formed will be refused again. `rate_limited`: This server's own per-minute search budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_filters`: No search query or filter was provided. `unrecognized_exclusion`: An exclude_allergens or exclude_traces value is not an allergen tag the Open Food Facts vocabulary confirms, or the vocabulary could not be reached to check it — an unrecognized exclusion would exclude nothing. `additives_filter_needs_tag_search`: additives_tag was combined with a query or nutrient_filters, which route to a backend that cannot filter by additive. `query_too_long`: query carries more than 24 words, more than the text backend can require at once. `page_out_of_range`: A search by tag filters alone asks for a page past 10, or a search the text backend serves asks for page * page_size beyond its 10000-result window. `upstream_error`: Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, reports a search-engine failure inside an HTTP 200, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request deadline, or answered 408, 425, or 504. `upstream_rejected`: Open Food Facts refuses the request with a 4xx other than 408, 425, or 429, or with 501 Not Implemented — the request as formed will be refused again. `rate_limited`: This server's own per-minute search budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler."
3 tool updates
- Changed
off_compare_products5 fields changed- changed
Input schema / properties / barcodes / items / descriptionPrevious value: -"EAN-13 or UPC barcode (8–14 digits)."New value: +"Product barcode, digits only: 4–40 digits after any leading zeros." - changed
Input schema / properties / barcodes / items / patternPrevious value: -"^\\d{8,14}$"New value: +"^0*[1-9]\\d{3,39}$" - changed
Output schema / properties / failed / items / properties / barcode / descriptionPrevious value: -"EAN-13 or UPC barcode whose fetch failed."New value: +"Barcode whose fetch failed, as provided in input." - changed
Output schema / properties / not_found / items / descriptionPrevious value: -"EAN-13 or UPC barcode with no contributor record."New value: +"Barcode with no contributor record, as provided in input." - changed
Output schema / properties / products / items / properties / barcode / descriptionPrevious value: -"EAN-13 or UPC barcode (same as provided input)."New value: +"Barcode, echoed exactly as provided in input."
- Changed
off_get_product2 fields changed- changed
Input schema / properties / barcode / descriptionPrevious value: -"EAN-13 or UPC barcode (8–14 digits). The primary key for Open Food Facts. Example: \"3017620422003\" (Nutella FR)."New value: +"Product barcode, digits only: 4–40 digits after any leading zeros. The primary key for Open Food Facts — the barcode of an off_search_products row works as is. Example: \"3017620422003\" (Nutella FR)." - changed
Input schema / properties / barcode / patternPrevious value: -"^\\d{8,14}$"New value: +"^0*[1-9]\\d{3,39}$"
- Changed
off_search_products18 fields changed- changed
Input schema / properties / allergens_tag / descriptionPrevious value: -"Canonical allergen tag ID. Example: \"en:milk\", \"en:gluten\". Use off_browse_taxonomy with facet=\"allergens\". Selects products that declare this allergen; it cannot select allergen-free products, because a product with no allergen tags may simply have none entered yet."New value: +"Canonical allergen tag ID. Example: \"en:milk\", \"en:gluten\". Use off_browse_taxonomy with facet=\"allergens\". Selects products that declare this allergen; it cannot select allergen-free products, because a product with no allergen tags may simply have none entered yet. To leave an allergen out, use exclude_allergens." - changed
Input schema / properties / brands_tag / descriptionPrevious value: -"Brand slug (lowercased, hyphenated). Example: \"nutella\", \"kelloggs\". Matched exactly against the normalized slug — a partial or misspelled slug matches nothing rather than falling back to a near match, so put open-ended brand wording in query instead."New value: +"Brand slug (lowercased, hyphenated). Example: \"nutella\", \"kelloggs\". A brand name is slugged the way Open Food Facts slugs it (\"Ben & Jerry's\" → \"ben-jerry-s\") and then matched exactly — a partial or misspelled slug matches nothing rather than falling back to a near match, so put open-ended brand wording in query instead." - added
Input schema / properties / exclude_allergensAdded value: +{ + "description": "Allergen tag IDs a product must not declare, all applied. Example: [\"en:nuts\", \"en:peanuts\"]. Each value must be an allergen tag Open Food Facts recognizes — resolve it with off_browse_taxonomy facet=\"allergens\" — and one it does not recognize is rejected rather than sent, because it would exclude nothing. A product with no allergen data entered passes an exclusion, so check a candidate with off_get_product before relying on it.", + "items": { + "description": "One canonical allergen tag ID to exclude, e.g. \"en:nuts\".", + "type": "string" + }, + "maxItems": 14, + "type": "array" +} - added
Input schema / properties / exclude_tracesAdded value: +{ + "description": "Allergen tag IDs a product's label must not warn it may contain as traces, all applied. Example: [\"en:nuts\"]. Values are validated like exclude_allergens. A product with no trace data entered passes, so check a candidate with off_get_product before relying on it.", + "items": { + "description": "One canonical allergen tag ID to exclude as a trace, e.g. \"en:nuts\".", + "type": "string" + }, + "maxItems": 14, + "type": "array" +} - added
Input schema / properties / ingredients_analysis_tagAdded value: +{ + "description": "Vegan, vegetarian, or palm-oil verdict Open Food Facts computes from the parsed ingredients. Example: \"en:vegan\", \"en:palm-oil-free\". \"en:maybe-vegan\" and \"en:may-contain-palm-oil\" mean the ingredients could not settle it, and the \"-unknown\" values mean no verdict could be computed.", + "enum": [ + "en:palm-oil", + "en:palm-oil-free", + "en:may-contain-palm-oil", + "en:palm-oil-content-unknown", + "en:vegan", + "en:maybe-vegan", + "en:non-vegan", + "en:vegan-status-unknown", + "en:vegetarian", + "en:maybe-vegetarian", + "en:non-vegetarian", + "en:vegetarian-status-unknown" + ], + "type": "string" +} - added
Input schema / properties / labels_tag / anyOfAdded value: +[ + { + "description": "One canonical label tag ID.", + "type": "string" + }, + { + "description": "Up to 10 canonical label tag IDs, all of which must apply.", + "items": { + "description": "One canonical label tag ID.", + "type": "string" + }, + "maxItems": 10, + "type": "array" + } +] - changed
Input schema / properties / labels_tag / descriptionPrevious value: -"Canonical label/certification tag ID. Example: \"en:organic\", \"en:fair-trade\", \"en:no-gluten\". Use off_browse_taxonomy with facet=\"labels\"."New value: +"Canonical label/certification tag ID, or an array of up to 10 that must all apply. Example: \"en:organic\", or [\"en:organic\", \"en:fair-trade\"] for products carrying both. Use off_browse_taxonomy with facet=\"labels\"." - removed
Input schema / properties / labels_tag / typeRemoved value: -"string" - changed
Input schema / properties / page / descriptionPrevious value: -"Page number (1-based). Use with page_size to paginate results. Searches that include a text query serve only the first 10,000 results, so page * page_size must stay at or below 10,000 — a deeper request is rejected rather than sent. Tag-only searches have no published window, but Open Food Facts refuses deep pages unpredictably; narrowing the filters is more reliable than paging far in."New value: +"Page number (1-based). Use with page_size to paginate results. A search by tag filters alone is served through page 10 only, so at page_size 50 it reaches the first 500 matches. A search carrying query or nutrient_filters serves only the first 10000 results, so page * page_size must stay at or below 10000. A request past either bound is rejected rather than sent; narrow the filters or change sort_by to bring other products forward." - changed
Input schema / properties / query / descriptionPrevious value: -"Full-text search term across product names, brands, and ingredients. Combines with any tag filters — results match this text and satisfy the filters. Example: \"dark chocolate 70%\". Supplying it routes the search to the text index, a snapshot that lags the live Open Food Facts database; drop it to run the same tag filters against the current data."New value: +"Words to find. Every word must match the product name, generic name, categories, labels, or brand — ingredients and quantity are not searched — so put only words the product itself would carry. Stop words of English, French, Spanish, German, and Italian (\"with\", \"the\", \"de\", \"mit\", …) are not required, and neither is a content word that is a stop word in one of them (such as Spanish \"soy\"), though it still ranks the results. Names are matched in the 31 languages the text index analyzes, so a product named only in French is found by its French name. At most 24 words, counting each part of a hyphenated word. Example: \"dark chocolate 70%\". Supplying it routes the search to the text index, a snapshot that lags the live Open Food Facts database; drop it to run the same tag filters against the current data." - added
Input schema / properties / traces_tagAdded value: +{ + "description": "Canonical allergen tag ID the label warns the product may contain as a trace (\"may contain nuts\"). Example: \"en:nuts\". Trace tags are allergen tags, so off_browse_taxonomy with facet=\"allergens\" resolves them. Selects products carrying the warning; to leave them out, use exclude_traces.", + "type": "string" +} - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_filters`: No search query or filter was provided. `additives_filter_needs_tag_search`: additives_tag was combined with a query or nutrient_filters, which route to a backend that cannot filter by additive. `page_out_of_range`: A search the text backend serves asks for page * page_size beyond the 10000-result window Open Food Facts offers. `upstream_error`: Open Food Facts returns a 5xx other than 501, serves an HTML error page with a 2xx or 5xx status, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request deadline. `upstream_rejected`: Open Food Facts answers 4xx, including the 401 it serves for a page too deep, or 501 Not Implemented — the request as formed will be refused again. `rate_limited`: This server's own per-minute search budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_filters`: No search query or filter was provided. `unrecognized_exclusion`: An exclude_allergens or exclude_traces value is not an allergen tag the Open Food Facts vocabulary confirms, or the vocabulary could not be reached to check it — an unrecognized exclusion would exclude nothing. `additives_filter_needs_tag_search`: additives_tag was combined with a query or nutrient_filters, which route to a backend that cannot filter by additive. `query_too_long`: query carries more than 24 words, more than the text backend can require at once. `page_out_of_range`: A search by tag filters alone asks for a page past 10, or a search the text backend serves asks for page * page_size beyond its 10000-result window. `upstream_error`: Open Food Facts returns a 5xx other than 501, serves an HTML error page with a 2xx or 5xx status, reports a search-engine failure inside an HTTP 200, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request deadline. `upstream_rejected`: Open Food Facts answers 4xx or 501 Not Implemented — the request as formed will be refused again. `rate_limited`: This server's own per-minute search budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / error / properties / data / properties / reason / examplesPrevious value: -[ - "no_filters", - "additives_filter_needs_tag_search", - "page_out_of_range", - "upstream_error", - "upstream_timeout", - "upstream_rejected", - "rate_limited" -]New value: +[ + "no_filters", + "unrecognized_exclusion", + "additives_filter_needs_tag_search", + "query_too_long", + "page_out_of_range", + "upstream_error", + "upstream_timeout", + "upstream_rejected", + "rate_limited" +] - added
Output schema / properties / exclusion_coverageAdded value: +{ + "description": "Present only on searches carrying exclude_allergens or exclude_traces. States that products with no allergen or trace data entered pass an exclusion, so a result is not confirmed free of the excluded allergens, and names the off_get_product fields to check.", + "type": "string" +} - changed
Output schema / properties / last_page / descriptionPrevious value: -"Deepest page of this result set that holds products, at the page_size used — capped by the 10,000-result window on a search the text index answers. Absent when total_is_lower_bound is true — the total it would divide is the ceiling the backend stopped counting at, so no exact last page exists — and when nothing matched at all. On a tag-only search Open Food Facts can still refuse a deep page, so narrowing the filters beats paging out to this bound."New value: +"Deepest page of this result set that holds products and can be requested, at the page_size used — capped at page 10 on a search by tag filters alone and by the 10000-result window on a search the text index answers. Absent when total_is_lower_bound is true — the total it would divide is the ceiling the backend stopped counting at, so no exact last page exists — and when nothing matched at all." - added
Output schema / properties / omittedAdded value: +{ + "description": "Matches on this page left off because Open Food Facts stores them under a code it cannot serve (not 4–40 digits once leading zeros are stripped), so off_get_product could not look them up either. Absent when none was. total still counts them.", + "type": "number" +} - changed
Output schema / properties / page_count / descriptionPrevious value: -"Products returned on this page (mirrors page_size except on the last page). Not the total number of pages."New value: +"Products returned on this page — page_size except on the last page, or when a match stored under a code Open Food Facts cannot serve was left off. Not the total number of pages." - changed
Output schema / properties / products / items / properties / barcode / descriptionPrevious value: -"EAN/UPC barcode. Pass to off_get_product for full details."New value: +"Product barcode, 4–40 digits after any leading zeros — a code off_get_product accepts as is, so pass it there for full details. A match stored under a code Open Food Facts cannot serve is left off the page."
4 tool updates
- Changed
off_browse_taxonomy2 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum entries to return (1–100, default 20). There is no offset or page input: Open Food Facts returns only the first `limit` matches for a term and offers no cursor, so narrow the search term rather than paging."New value: +"Maximum entries to return (1–100, default 20). There is no offset or page input: Open Food Facts offers no cursor for this lookup, so narrow the search term rather than paging. The tag spelling the term itself (e.g. \"lentil\" → en:lentils) is listed first among the live matches, so it is not the one a small limit cuts." - removed
Output schema / properties / tags / items / properties / productsRemoved value: -{ - "description": "Approximate count of products with this tag. Not available for all facets.", - "type": "number" -}
- Changed
off_compare_products3 fields changed- changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `upstream_error`: Open Food Facts returns 5xx, serves an HTML error page, or is unreachable — surfaced per barcode in failed[] `upstream_timeout`: Open Food Facts did not answer within the request deadline — surfaced per barcode in failed[] `upstream_rejected`: Open Food Facts answers 4xx for a barcode — surfaced per barcode in failed[] `rate_limited`: This server's own per-minute request budget is spent, or Open Food Facts answers 429 — surfaced per barcode in failed[] Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `upstream_error`: Open Food Facts returns a 5xx other than 501, serves an HTML error page with a 2xx or 5xx status, or is unreachable — surfaced per barcode in failed[]. `upstream_timeout`: Open Food Facts did not answer within the request deadline — surfaced per barcode in failed[]. `upstream_rejected`: Open Food Facts answers 4xx or 501 Not Implemented for a barcode — surfaced per barcode in failed[]. `rate_limited`: This server's own per-minute request budget is spent, or Open Food Facts answers 429 — surfaced per barcode in failed[]. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / products / items / properties / ecoscore_grade / descriptionPrevious value: -"Green-Score/Eco-Score (a–e or \"unknown\"). Often absent."New value: +"Green-Score (formerly Eco-Score) environmental impact grade: \"a-plus\" (lowest impact), then \"a\" through \"f\"; \"unknown\" when the data it needs is missing, or \"not-applicable\" for product categories the score does not cover. Often absent." - changed
Output schema / properties / products / items / properties / nutriscore_grade / descriptionPrevious value: -"Nutri-Score letter (a–e). Absent when not computed."New value: +"Nutri-Score grade: \"a\" through \"e\", \"unknown\" when the nutrition data entered is not enough to compute it, or \"not-applicable\" for product categories the score does not cover. Absent when Open Food Facts sent none."
- Changed
off_get_product8 fields changed- changed
Output schema / properties / barcode / descriptionPrevious value: -"Barcode as returned by the API."New value: +"The input barcode, echoed back unchanged. Open Food Facts can hold the record under another form of the same code (030000010402 resolves to the record stored as 0030000010402); that stored form is not reported." - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `not_found`: Barcode status:0 — not present in any contributor record `upstream_error`: Open Food Facts returns 5xx, serves an HTML error page, or is unreachable `upstream_timeout`: Open Food Facts did not answer within the request deadline `upstream_rejected`: Open Food Facts answers 4xx for something other than a missing barcode `rate_limited`: This server's own per-minute request budget is spent, or Open Food Facts answers 429 Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `not_found`: Barcode status:0 — not present in any contributor record. `upstream_error`: Open Food Facts returns a 5xx other than 501, serves an HTML error page with a 2xx or 5xx status, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request deadline. `upstream_rejected`: Open Food Facts answers 4xx for something other than a missing barcode, or 501 Not Implemented. `rate_limited`: This server's own per-minute request budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / product / properties / ecoscore_grade / descriptionPrevious value: -"Green-Score/Eco-Score environmental impact letter (a–e, or \"unknown\"). Highly variable — depends on packaging, origins, and transport data completeness."New value: +"Green-Score (formerly Eco-Score) environmental impact grade: \"a-plus\" (lowest impact), then \"a\" through \"f\"; \"unknown\" when the data it needs is missing, or \"not-applicable\" for product categories the score does not cover. Highly variable — depends on packaging, origins, and transport data completeness." - changed
Output schema / properties / product / properties / ingredients / descriptionPrevious value: -"Parsed ingredient list. Absent when not yet parsed by contributors."New value: +"Parsed ingredient list, top level in label order, each entry carrying its sub-ingredients. Absent when not yet parsed by contributors." - changed
Output schema / properties / product / properties / ingredients / items / descriptionPrevious value: -"A single parsed ingredient entry."New value: +"A single top-level parsed ingredient entry." - added
Output schema / properties / product / properties / ingredients / items / properties / ingredientsAdded value: +{ + "description": "Sub-ingredients of this entry (e.g. the flours under \"cereal\"), in the same entry shape and nested up to two more levels. Absent when the entry has none.", + "items": { + "additionalProperties": false, + "description": "A sub-ingredient of a top-level entry.", + "properties": { + "id": { + "description": "Canonical ingredient ID (e.g. \"en:sugar\", \"en:salt\").", + "type": "string" + }, + "ingredients": { + "description": "Sub-ingredients of this entry. Anything nested deeper upstream is listed here as well, right after the entry it belongs under, so no entry is dropped. Absent when the entry has none.", + "items": { + "additionalProperties": false, + "description": "A sub-ingredient at the third level. An entry nested deeper upstream is listed at this level too, directly after the entry it belongs under.", + "properties": { + "id": { + "description": "Canonical ingredient ID (e.g. \"en:sugar\", \"en:salt\").", + "type": "string" + }, + "percent_estimate": { + "description": "Estimated share of the whole product, in percent. On a sub-ingredient it is still a share of the whole product, not of its parent — a parent's estimate already includes its sub-ingredients, so summing across levels double-counts.", + "type": "number" + }, + "text": { + "description": "Ingredient name as it appears in the list.", + "type": "string" + }, + "vegan": { + "description": "\"yes\", \"no\", or \"maybe\" — absent when unknown.", + "type": "string" + }, + "vegetarian": { + "description": "\"yes\", \"no\", or \"maybe\" — absent when unknown.", + "type": "string" + } + }, + "required": [ + "text" + ], + "type": "object" + }, + "type": "array" + }, + "percent_estimate": { + "description": "Estimated share of the whole product, in percent. On a sub-ingredient it is still a share of the whole product, not of its parent — a parent's estimate already includes its sub-ingredients, so summing across levels double-counts.", + "type": "number" + }, + "text": { + "description": "Ingredient name as it appears in the list.", + "type": "string" + }, + "vegan": { + "description": "\"yes\", \"no\", or \"maybe\" — absent when unknown.", + "type": "string" + }, + "vegetarian": { + "description": "\"yes\", \"no\", or \"maybe\" — absent when unknown.", + "type": "string" + } + }, + "required": [ + "text" + ], + "type": "object" + }, + "type": "array" +} - changed
Output schema / properties / product / properties / ingredients / items / properties / percent_estimate / descriptionPrevious value: -"Estimated percentage of this ingredient."New value: +"Estimated share of the whole product, in percent. On a sub-ingredient it is still a share of the whole product, not of its parent — a parent's estimate already includes its sub-ingredients, so summing across levels double-counts." - changed
Output schema / properties / product / properties / nutriscore_grade / descriptionPrevious value: -"Nutri-Score letter (a–e, lowercase). \"a\" is highest nutritional quality. Absent when not enough nutrition data to compute. Regional formula variants exist."New value: +"Nutri-Score grade, lowercase: \"a\" (highest nutritional quality) through \"e\", \"unknown\" when the nutrition data entered is not enough to compute it, or \"not-applicable\" for product categories the score does not cover. Absent when Open Food Facts sent none. Regional formula variants exist."
- Changed
off_search_products3 fields changed- changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_filters`: No search query or filter was provided `additives_filter_needs_tag_search`: additives_tag was combined with a query or nutrient_filters, which route to a backend that cannot filter by additive `page_out_of_range`: A search the text backend serves asks for page * page_size beyond the 10000-result window Open Food Facts offers `upstream_error`: Open Food Facts returns 5xx, serves an HTML error page, or is unreachable `upstream_timeout`: Open Food Facts did not answer within the request deadline `upstream_rejected`: Open Food Facts answers 4xx — the request as formed will be refused again `rate_limited`: This server's own per-minute search budget is spent, or Open Food Facts answers 429 Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_filters`: No search query or filter was provided. `additives_filter_needs_tag_search`: additives_tag was combined with a query or nutrient_filters, which route to a backend that cannot filter by additive. `page_out_of_range`: A search the text backend serves asks for page * page_size beyond the 10000-result window Open Food Facts offers. `upstream_error`: Open Food Facts returns a 5xx other than 501, serves an HTML error page with a 2xx or 5xx status, or is unreachable. `upstream_timeout`: Open Food Facts did not answer within the request deadline. `upstream_rejected`: Open Food Facts answers 4xx, including the 401 it serves for a page too deep, or 501 Not Implemented — the request as formed will be refused again. `rate_limited`: This server's own per-minute search budget is spent, or Open Food Facts answers 429. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / products / items / properties / ecoscore_grade / descriptionPrevious value: -"Green-Score letter (a–e). Environmental impact indicator. Absent when not computed."New value: +"Green-Score environmental impact grade: \"a-plus\" (lowest impact), then \"a\" through \"f\"; \"unknown\" when the data it needs is missing, or \"not-applicable\" for product categories the score does not cover. Absent when Open Food Facts sent none." - changed
Output schema / properties / products / items / properties / nutriscore_grade / descriptionPrevious value: -"Nutri-Score letter (a–e). Absent when not computed."New value: +"Nutri-Score grade: \"a\" through \"e\", \"unknown\" when the nutrition data entered is not enough to compute it, or \"not-applicable\" for product categories the score does not cover. Absent when Open Food Facts sent none."
1 tool update
- Changed
off_get_product6 fields changed- changed
Input schema / properties / fields / descriptionPrevious value: -"Subset of fields to return. Omitting returns all standard fields. Use to reduce payload when only scores or ingredients are needed."New value: +"Subset of fields to return. Omitting returns all standard fields. Use to reduce payload when only scores or ingredients are needed. A field that cannot be read on its own arrives with what it depends on: nutriments brings serving_size, serving_quantity, and serving_quantity_unit so per-serving figures carry their denominator, and serving_quantity_unit brings the quantity it describes. requested_fields echoes the full set that was fetched." - changed
Input schema / properties / fields / items / enumPrevious value: -[ - "product_name", - "brands", - "quantity", - "ingredients_text", - "ingredients", - "allergens_tags", - "additives_tags", - "nutriscore_grade", - "nova_group", - "ecoscore_grade", - "nutriments", - "serving_size", - "serving_quantity", - "serving_quantity_unit", - "categories_tags", - "labels_tags", - "packaging_tags", - "origins_tags", - "image_url", - "completeness", - "data_quality_tags" -]New value: +[ + "product_name", + "brands", + "quantity", + "ingredients_text", + "ingredients", + "allergens_tags", + "traces_tags", + "additives_tags", + "ingredients_analysis_tags", + "nutriscore_grade", + "nova_group", + "ecoscore_grade", + "nutriments", + "serving_size", + "serving_quantity", + "serving_quantity_unit", + "categories_tags", + "labels_tags", + "packaging_tags", + "origins_tags", + "countries_tags", + "image_url", + "completeness", + "data_quality_tags" +] - added
Output schema / properties / product / properties / countries_tagsAdded value: +{ + "description": "Countries where the product is sold — the same values off_search_products accepts as countries_tag. Distinct from origins_tags, which is where the ingredients come from.", + "items": { + "description": "Canonical country tag ID (e.g. \"en:france\").", + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / product / properties / ingredients_analysis_tagsAdded value: +{ + "description": "Product-level vegan, vegetarian, and palm-oil verdicts computed by Open Food Facts from the parsed ingredients. \"maybe-\" and \"-status-unknown\" values mean the ingredients could not settle it (e.g. \"en:maybe-vegan\").", + "items": { + "description": "Analysis verdict tag (e.g. \"en:non-vegan\", \"en:palm-oil-free\").", + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / product / properties / traces_tagsAdded value: +{ + "description": "Allergens the label says the product may contain as traces, from cross-contamination warnings. Distinct from allergens_tags, which carries allergens declared in the ingredients. [\"en:none\"] means the label states no traces; an empty array or an absent field means not yet entered, not trace-free. Values resolve through off_browse_taxonomy's allergens facet.", + "items": { + "description": "Canonical allergen tag ID (e.g. \"en:nuts\").", + "type": "string" + }, + "type": "array" +} - changed
Output schema / properties / requested_fields / descriptionPrevious value: -"The field subset that was requested, when the caller passed `fields`. Absent means all standard fields were requested. Sections outside this subset are omitted because they were not requested — not because Open Food Facts lacks the data."New value: +"The field subset that was fetched, when the caller passed `fields` — the requested fields plus the ones they depend on, so every field that can appear in `product` is named here. Absent means all standard fields were requested. Sections outside this subset are omitted because they were not requested — not because Open Food Facts lacks the data."
2 tool updates
- Changed
off_browse_taxonomy1 field changed- changed
Input schema / properties / search / descriptionPrevious value: -"Term to resolve. Matched case-insensitively as a substring of the tag ID or display name. A single word works best (\"hummus\", not \"hummus dip\"). Omit only to see a small reference list — Open Food Facts cannot list the full vocabulary without a term, so an unfiltered call is not a view of the full facet."New value: +"Term to resolve. Matched case-insensitively as a substring of the tag ID, the display name, or a common synonym of either (\"shellfish\" resolves to en:crustaceans, \"gluten free\" to en:no-gluten). A single word works best (\"hummus\", not \"hummus dip\"). Omit only to see a small reference list — Open Food Facts cannot list the full vocabulary without a term, so an unfiltered call is not a view of the full facet."
- Changed
off_search_products7 fields changed- changed
Input schema / properties / additives_tag / descriptionPrevious value: -"Canonical additive (E-number) tag ID. Example: \"en:e322\", \"en:e330\". Use off_browse_taxonomy with facet=\"additives\". Available only on searches with no query — full-text searches cannot filter by additive, so combining the two is rejected instead of silently returning nothing."New value: +"Canonical additive (E-number) tag ID. Example: \"en:e322\", \"en:e330\". Use off_browse_taxonomy with facet=\"additives\". Available only on searches carrying neither query nor nutrient_filters — both route to a backend with no additives field, so combining them is rejected instead of silently returning nothing." - added
Input schema / properties / nutrient_filtersAdded value: +{ + "description": "Numeric constraints on nutrient values per 100 g, combined as AND with each other and with every other filter. Pair two entries on the same nutrient to express a range (e.g. sugars gte 2 and sugars lte 8). Served only by the text backend, so supplying one routes the search there even without query — it then reads the lagging text index and is subject to the 10,000-result page window, and additives_tag cannot be combined with it. Per-serving and prepared-product values are not searchable.", + "items": { + "description": "One numeric constraint on a per-100 g nutrient value.", + "properties": { + "nutrient": { + "description": "Nutrient to constrain, measured per 100 g. Energy is kilocalories; every other value is grams per 100 g.", + "enum": [ + "energy-kcal", + "fat", + "saturated-fat", + "carbohydrates", + "sugars", + "fiber", + "proteins", + "salt", + "sodium" + ], + "type": "string" + }, + "operator": { + "description": "Comparison against value: \"lt\" below, \"lte\" at or below, \"gt\" above, \"gte\" at or above.", + "enum": [ + "lt", + "lte", + "gt", + "gte" + ], + "type": "string" + }, + "value": { + "description": "Threshold to compare against, in the nutrient's per-100 g unit.", + "minimum": 0, + "type": "number" + } + }, + "required": [ + "nutrient", + "operator", + "value" + ], + "type": "object" + }, + "maxItems": 18, + "type": "array" +} - changed
Input schema / properties / query / descriptionPrevious value: -"Full-text search term across product names, brands, and ingredients. Combines with any tag filters — results match this text and satisfy the filters. Example: \"dark chocolate 70%\"."New value: +"Full-text search term across product names, brands, and ingredients. Combines with any tag filters — results match this text and satisfy the filters. Example: \"dark chocolate 70%\". Supplying it routes the search to the text index, a snapshot that lags the live Open Food Facts database; drop it to run the same tag filters against the current data." - changed
Input schema / properties / sort_by / descriptionPrevious value: -"Sort order for searches without a text query. \"unique_scans_n\" surfaces the most-scanned products; omitting returns results in default order. Searches that include a text query are relevance-ranked and ignore this option."New value: +"Sort order, applied on every search. Each value orders newest or highest first: \"unique_scans_n\" surfaces the most-scanned products, \"last_modified_t\" and \"created_t\" the most recently updated and newest records, \"popularity_key\" the most popular. Omitting it leaves text searches relevance-ranked and tag-only searches in the default order." - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_filters`: No search query or filter was provided `additives_filter_needs_tag_search`: additives_tag was combined with a text query, which cannot filter by additive `page_out_of_range`: A text search asks for page * page_size beyond the 10000-result window Open Food Facts serves `upstream_error`: Open Food Facts returns 5xx, serves an HTML error page, or is unreachable `upstream_timeout`: Open Food Facts did not answer within the request deadline `upstream_rejected`: Open Food Facts answers 4xx — the request as formed will be refused again `rate_limited`: This server's own per-minute search budget is spent, or Open Food Facts answers 429 Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_filters`: No search query or filter was provided `additives_filter_needs_tag_search`: additives_tag was combined with a query or nutrient_filters, which route to a backend that cannot filter by additive `page_out_of_range`: A search the text backend serves asks for page * page_size beyond the 10000-result window Open Food Facts offers `upstream_error`: Open Food Facts returns 5xx, serves an HTML error page, or is unreachable `upstream_timeout`: Open Food Facts did not answer within the request deadline `upstream_rejected`: Open Food Facts answers 4xx — the request as formed will be refused again `rate_limited`: This server's own per-minute search budget is spent, or Open Food Facts answers 429 Other values are possible when a failure originates below the handler." - added
Output schema / properties / last_pageAdded value: +{ + "description": "Deepest page of this result set that holds products, at the page_size used — capped by the 10,000-result window on a search the text index answers. Absent when total_is_lower_bound is true — the total it would divide is the ceiling the backend stopped counting at, so no exact last page exists — and when nothing matched at all. On a tag-only search Open Food Facts can still refuse a deep page, so narrowing the filters beats paging out to this bound.", + "type": "number" +} - added
Output schema / properties / text_index_snapshotAdded value: +{ + "description": "Present only on searches the text backend answered. States that those results come from an index snapshot that lags the live Open Food Facts database, so a recently contributed product can be missing from them while the tag-only path still returns it. Absent on tag-only searches, which read the live database.", + "type": "string" +}
4 tool updates
- Changed
off_browse_taxonomy11 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / facet / descriptionPrevious value: -"\"categories\" covers food categories (en:cheeses, en:breakfast-cereals). \"labels\" covers certifications (en:organic, en:fair-trade). \"allergens\" covers declared allergens (en:milk, en:gluten). \"additives\" covers E-numbers (en:e322). \"countries\" covers country-of-sale tags (en:france). \"nova_groups\" and \"nutrition_grades\" are closed vocabularies answered offline and returned complete; the other five are resolved against the live Open Food Facts taxonomy."New value: +"\"categories\" covers food categories (en:cheeses, en:breakfast-cereals). \"labels\" covers certifications (en:organic, en:fair-trade). \"allergens\" covers declared allergens (en:milk, en:gluten). \"additives\" covers E-numbers (en:e322). \"countries\" covers country-of-sale tags (en:france). \"nova_groups\" and \"nutrition_grades\" are closed vocabularies returned complete; the other five are resolved against the Open Food Facts taxonomy." - changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum entries to return (1–100, default 20). There is no offset or page input: the upstream taxonomy endpoint serves only the first `limit` matches for a term and offers no cursor, so narrow the search term rather than paging."New value: +"Maximum entries to return (1–100, default 20). There is no offset or page input: Open Food Facts returns only the first `limit` matches for a term and offers no cursor, so narrow the search term rather than paging." - changed
Input schema / properties / search / descriptionPrevious value: -"Term to resolve. Matched case-insensitively as a substring of the tag ID or display name, against both the live Open Food Facts vocabulary and this server's offline sample. A single word works best (\"hummus\", not \"hummus dip\"). Omit only to see the offline sample — the live vocabulary cannot be listed without a term, so an unfiltered call is not a view of the full facet."New value: +"Term to resolve. Matched case-insensitively as a substring of the tag ID or display name. A single word works best (\"hummus\", not \"hummus dip\"). Omit only to see a small reference list — Open Food Facts cannot list the full vocabulary without a term, so an unfiltered call is not a view of the full facet." - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "facet", + "tags" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode.", + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - changed
Output schema / properties / notice / descriptionPrevious value: -"Caveat about how this answer was produced — that the listing is the offline sample rather than the live vocabulary, that the live vocabulary was unreachable, or that nothing matched and why."New value: +"Caveat about the answer — that the listing is a limited reference list rather than the full vocabulary, that Open Food Facts was unreachable, or that nothing matched and why." - changed
Output schema / properties / total_in_facet / descriptionPrevious value: -"Total entries in this facet. Present only for nova_groups and nutrition_grades, whose vocabularies are closed and complete here. Absent for the live facets: the Open Food Facts taxonomy endpoint reports no match total and cannot be enumerated, so no figure would be a real one."New value: +"Total entries in this facet. Present only for nova_groups and nutrition_grades, whose vocabularies are closed and complete. Absent for the other facets: Open Food Facts reports no match total and cannot enumerate them, so no figure would be a real one." - removed
Output schema / requiredRemoved value: -[ - "facet", - "tags" -]
- Changed
off_compare_products6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "products", + "succeeded", + "not_found" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `upstream_error`: Open Food Facts returns 5xx, serves an HTML error page, or is unreachable — surfaced per barcode in failed[] `upstream_timeout`: Open Food Facts did not answer within the request deadline — surfaced per barcode in failed[] `upstream_rejected`: Open Food Facts answers 4xx for a barcode — surfaced per barcode in failed[] `rate_limited`: This server's own per-minute request budget is spent, or Open Food Facts answers 429 — surfaced per barcode in failed[] Other values are possible when a failure originates below the handler.", + "examples": [ + "upstream_error", + "upstream_timeout", + "upstream_rejected", + "rate_limited" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "products", - "succeeded", - "not_found" -]
- Changed
off_get_product6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "barcode", + "product" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `not_found`: Barcode status:0 — not present in any contributor record `upstream_error`: Open Food Facts returns 5xx, serves an HTML error page, or is unreachable `upstream_timeout`: Open Food Facts did not answer within the request deadline `upstream_rejected`: Open Food Facts answers 4xx for something other than a missing barcode `rate_limited`: This server's own per-minute request budget is spent, or Open Food Facts answers 429 Other values are possible when a failure originates below the handler.", + "examples": [ + "not_found", + "upstream_error", + "upstream_timeout", + "upstream_rejected", + "rate_limited" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "barcode", - "product" -]
- Changed
off_search_products7 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / additives_tag / descriptionPrevious value: -"Canonical additive (E-number) tag ID. Example: \"en:e322\", \"en:e330\". Use off_browse_taxonomy with facet=\"additives\". Available only on searches with no query — the text backend does not index additives, so combining the two is rejected instead of silently returning nothing."New value: +"Canonical additive (E-number) tag ID. Example: \"en:e322\", \"en:e330\". Use off_browse_taxonomy with facet=\"additives\". Available only on searches with no query — full-text searches cannot filter by additive, so combining the two is rejected instead of silently returning nothing." - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "total", + "total_is_lower_bound", + "page", + "page_count", + "products" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `no_filters`: No search query or filter was provided `additives_filter_needs_tag_search`: additives_tag was combined with a text query, which cannot filter by additive `page_out_of_range`: A text search asks for page * page_size beyond the 10000-result window Open Food Facts serves `upstream_error`: Open Food Facts returns 5xx, serves an HTML error page, or is unreachable `upstream_timeout`: Open Food Facts did not answer within the request deadline `upstream_rejected`: Open Food Facts answers 4xx — the request as formed will be refused again `rate_limited`: This server's own per-minute search budget is spent, or Open Food Facts answers 429 Other values are possible when a failure originates below the handler.", + "examples": [ + "no_filters", + "additives_filter_needs_tag_search", + "page_out_of_range", + "upstream_error", + "upstream_timeout", + "upstream_rejected", + "rate_limited" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "total", - "total_is_lower_bound", - "page", - "page_count", - "products" -]
1 tool update
- Changed
off_browse_taxonomy6 fields changed- changed
Input schema / properties / facet / descriptionPrevious value: -"\"categories\" covers food categories (en:cheeses, en:breakfast-cereals). \"labels\" covers certifications (en:organic, en:fair-trade). \"allergens\" covers declared allergens (en:milk, en:gluten). \"additives\" covers E-numbers (en:e322). \"countries\" covers country-of-sale tags (en:france). \"nova_groups\" and \"nutrition_grades\" return the complete fixed vocabularies."New value: +"\"categories\" covers food categories (en:cheeses, en:breakfast-cereals). \"labels\" covers certifications (en:organic, en:fair-trade). \"allergens\" covers declared allergens (en:milk, en:gluten). \"additives\" covers E-numbers (en:e322). \"countries\" covers country-of-sale tags (en:france). \"nova_groups\" and \"nutrition_grades\" are closed vocabularies answered offline and returned complete; the other five are resolved against the live Open Food Facts taxonomy." - changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum entries to return (1–100, default 20). The categories facet is broad; a search term narrows it to the relevant tags."New value: +"Maximum entries to return (1–100, default 20). There is no offset or page input: the upstream taxonomy endpoint serves only the first `limit` matches for a term and offers no cursor, so narrow the search term rather than paging." - changed
Input schema / properties / search / descriptionPrevious value: -"Case-insensitive substring filter against tag ID or display name. Example: \"gluten\" returns en:gluten, en:no-gluten. Omit to list all entries for the facet (may be large for categories)."New value: +"Term to resolve. Matched case-insensitively as a substring of the tag ID or display name, against both the live Open Food Facts vocabulary and this server's offline sample. A single word works best (\"hummus\", not \"hummus dip\"). Omit only to see the offline sample — the live vocabulary cannot be listed without a term, so an unfiltered call is not a view of the full facet." - added
Output schema / properties / noticeAdded value: +{ + "description": "Caveat about how this answer was produced — that the listing is the offline sample rather than the live vocabulary, that the live vocabulary was unreachable, or that nothing matched and why.", + "type": "string" +} - changed
Output schema / properties / tags / items / properties / id / descriptionPrevious value: -"Canonical tag ID (e.g. \"en:organic\"). Use this value in off_search_products filter parameters."New value: +"Canonical tag ID (e.g. \"en:organic\"; bare \"1\"–\"4\" for NOVA groups, bare \"a\"–\"e\" for Nutri-Score grades). Pass this value through to the matching off_search_products filter parameter unchanged." - changed
Output schema / properties / total_in_facet / descriptionPrevious value: -"Total entries in this facet before search filtering. Large for categories."New value: +"Total entries in this facet. Present only for nova_groups and nutrition_grades, whose vocabularies are closed and complete here. Absent for the live facets: the Open Food Facts taxonomy endpoint reports no match total and cannot be enumerated, so no figure would be a real one."
1 tool update
- Changed
off_get_product9 fields changed- changed
Input schema / properties / fields / items / enumPrevious value: -[ - "product_name", - "brands", - "quantity", - "ingredients_text", - "ingredients", - "allergens_tags", - "additives_tags", - "nutriscore_grade", - "nova_group", - "ecoscore_grade", - "nutriments", - "categories_tags", - "labels_tags", - "packaging_tags", - "origins_tags", - "image_url", - "completeness", - "data_quality_tags" -]New value: +[ + "product_name", + "brands", + "quantity", + "ingredients_text", + "ingredients", + "allergens_tags", + "additives_tags", + "nutriscore_grade", + "nova_group", + "ecoscore_grade", + "nutriments", + "serving_size", + "serving_quantity", + "serving_quantity_unit", + "categories_tags", + "labels_tags", + "packaging_tags", + "origins_tags", + "image_url", + "completeness", + "data_quality_tags" +] - removed
Output schema / properties / foundRemoved value: -{ - "description": "False when the barcode exists in no contributor record (status:0). A false result means no contributor has entered this product yet — not that the product does not exist.", - "type": "boolean" -} - changed
Output schema / properties / product / descriptionPrevious value: -"Product data. Absent when found is false."New value: +"Product data. Always present on a successful call — a barcode with no contributor record raises the not_found error instead of returning an empty result." - added
Output schema / properties / product / properties / nutriments / properties / additional_100gAdded value: +{ + "additionalProperties": { + "additionalProperties": false, + "description": "One nutrient figure with the unit it is expressed in.", + "properties": { + "unit": { + "description": "Unit the figure is expressed in (\"g\", \"kcal\", \"kJ\"). Absent when Open Food Facts records no unit for this nutrient.", + "type": "string" + }, + "value": { + "description": "The figure Open Food Facts reported per 100g.", + "type": "number" + } + }, + "required": [ + "value" + ], + "type": "object" + }, + "description": "Every other per-100g nutrient Open Food Facts holds, keyed by normalized name (calcium, iron, vitamin_c, trans_fat, added_sugars, cholesterol, energy in kJ, …). Excludes the named fields above, so a nutrient appears in exactly one place. Micronutrients are usually reported in grams, so calcium 0.071 g is 71 mg — read the unit rather than assuming.", + "propertyNames": { + "description": "Nutrient name, hyphens normalized to underscores.", + "type": "string" + }, + "type": "object" +} - added
Output schema / properties / product / properties / nutriments / properties / additional_servingAdded value: +{ + "additionalProperties": { + "additionalProperties": false, + "description": "One nutrient figure with the unit it is expressed in.", + "properties": { + "unit": { + "description": "Unit the figure is expressed in (\"g\", \"kcal\", \"kJ\"). Absent when Open Food Facts records no unit for this nutrient.", + "type": "string" + }, + "value": { + "description": "The figure Open Food Facts reported per serving.", + "type": "number" + } + }, + "required": [ + "value" + ], + "type": "object" + }, + "description": "The same nutrients per serving. Also carries the per-serving figures for macros that have a named per-100g field but no named per-serving one (saturated_fat, carbohydrates, fiber, proteins, salt, sodium). Check serving_size for the denominator these figures are measured against.", + "propertyNames": { + "description": "Nutrient name, hyphens normalized to underscores.", + "type": "string" + }, + "type": "object" +} - added
Output schema / properties / product / properties / serving_quantityAdded value: +{ + "description": "Serving size parsed to a number, in serving_quantity_unit. Absent when Open Food Facts could not parse the printed serving size.", + "type": "number" +} - added
Output schema / properties / product / properties / serving_quantity_unitAdded value: +{ + "description": "Unit of serving_quantity — usually \"g\" but \"ml\" for liquids, so it is not safe to assume grams. Absent when serving_quantity is absent or Open Food Facts records no unit.", + "type": "string" +} - added
Output schema / properties / product / properties / serving_sizeAdded value: +{ + "description": "Serving size as printed on the label (e.g. \"28 g\", \"1 can (12 fl oz)\"). The denominator for every per-serving figure. Absent when contributors have not entered one, in which case per-serving values cannot be converted to or from the per-100g values.", + "type": "string" +} - changed
Output schema / requiredPrevious value: -[ - "barcode", - "found" -]New value: +[ + "barcode", + "product" +]
1 tool update
- Changed
off_search_products6 fields changed- added
Input schema / properties / additives_tagAdded value: +{ + "description": "Canonical additive (E-number) tag ID. Example: \"en:e322\", \"en:e330\". Use off_browse_taxonomy with facet=\"additives\". Available only on searches with no query — the text backend does not index additives, so combining the two is rejected instead of silently returning nothing.", + "type": "string" +} - added
Input schema / properties / allergens_tagAdded value: +{ + "description": "Canonical allergen tag ID. Example: \"en:milk\", \"en:gluten\". Use off_browse_taxonomy with facet=\"allergens\". Selects products that declare this allergen; it cannot select allergen-free products, because a product with no allergen tags may simply have none entered yet.", + "type": "string" +} - changed
Input schema / properties / brands_tag / descriptionPrevious value: -"Brand slug (lowercased, hyphenated). Example: \"nutella\", \"kelloggs\". Fuzzy — partial matches may work."New value: +"Brand slug (lowercased, hyphenated). Example: \"nutella\", \"kelloggs\". Matched exactly against the normalized slug — a partial or misspelled slug matches nothing rather than falling back to a near match, so put open-ended brand wording in query instead." - changed
Output schema / properties / total / descriptionPrevious value: -"Total matching products in the database for this query."New value: +"Matching products in the database for this search. Exact unless total_is_lower_bound is true, in which case at least this many match and the real figure is unknown." - added
Output schema / properties / total_is_lower_boundAdded value: +{ + "description": "True when the backend stopped counting at its ceiling and total is a floor, not the match total. Only text searches can hit it; add filters to bring the result set under the ceiling and get an exact count.", + "type": "boolean" +} - changed
Output schema / requiredPrevious value: -[ - "total", - "page", - "page_count", - "products" -]New value: +[ + "total", + "total_is_lower_bound", + "page", + "page_count", + "products" +]
2 tool updates
- Changed
off_compare_products3 fields changed- added
Output schema / properties / failedAdded value: +{ + "description": "Barcodes whose fetch failed, with the per-barcode reason. Absent when every fetch completed. A barcode listed here is unknown, not absent from Open Food Facts — retry it with off_get_product before concluding anything about the product.", + "items": { + "additionalProperties": false, + "description": "A single barcode whose fetch failed.", + "properties": { + "barcode": { + "description": "EAN-13 or UPC barcode whose fetch failed.", + "type": "string" + }, + "error": { + "description": "What went wrong for this barcode and what to do about it.", + "type": "string" + }, + "reason": { + "description": "Declared failure reason — one of upstream_error, upstream_timeout, upstream_rejected, rate_limited.", + "type": "string" + } + }, + "required": [ + "barcode", + "reason", + "error" + ], + "type": "object" + }, + "type": "array" +} - changed
Output schema / properties / not_found / descriptionPrevious value: -"Barcodes with no contributor record. Not an error — the product may exist but not yet entered in Open Food Facts."New value: +"Barcodes Open Food Facts answered for, confirming no contributor record exists. Not an error — the product may exist but not yet be entered. Never used for a fetch that failed." - changed
Output schema / properties / products / descriptionPrevious value: -"Comparison rows, one per barcode in input order."New value: +"Comparison rows in input order — one per barcode whose fetch completed, whether or not a record exists. Barcodes whose fetch failed have no row here; they appear in failed."
- Changed
off_search_products2 fields changed- changed
Input schema / properties / page / descriptionPrevious value: -"Page number (1-based). Use with page_size to paginate results."New value: +"Page number (1-based). Use with page_size to paginate results. Searches that include a text query serve only the first 10,000 results, so page * page_size must stay at or below 10,000 — a deeper request is rejected rather than sent. Tag-only searches have no published window, but Open Food Facts refuses deep pages unpredictably; narrowing the filters is more reliable than paging far in." - changed
Output schema / properties / notice / descriptionPrevious value: -"Guidance when results are empty — echoes filters and suggests how to broaden."New value: +"Guidance about this result set — echoes the filters and suggests how to broaden when nothing matched, or names the current page and how far the backend will actually paginate when more results exist."
3 tool updates
- Changed
off_browse_taxonomy1 field changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum entries to return (1–100, default 20). Categories has many entries — always provide a search term when browsing categories."New value: +"Maximum entries to return (1–100, default 20). The categories facet is broad; a search term narrows it to the relevant tags."
- Changed
off_compare_products1 field changed- changed
Input schema / properties / barcodes / descriptionPrevious value: -"2–10 barcodes to compare. All products are fetched in parallel. Example: [\"3017620422003\", \"7622210100146\"]."New value: +"2–10 barcodes to compare, returned as one row each in input order. Example: [\"3017620422003\", \"7622210100146\"]."
- Changed
off_search_products2 fields changed- changed
Input schema / properties / query / descriptionPrevious value: -"Full-text search term across product names, brands, and ingredients. When provided, routes to the text search engine — tag filters (categories_tag, brands_tag, etc.) are ignored in this path. Example: \"dark chocolate 70%\"."New value: +"Full-text search term across product names, brands, and ingredients. Combines with any tag filters — results match this text and satisfy the filters. Example: \"dark chocolate 70%\"." - changed
Input schema / properties / sort_by / descriptionPrevious value: -"Sort order for tag-filter results. \"unique_scans_n\" surfaces the most-scanned products. Ignored on text-query searches (search.openfoodfacts.org does not support server-side sort). Omitting returns results in default database order."New value: +"Sort order for searches without a text query. \"unique_scans_n\" surfaces the most-scanned products; omitting returns results in default order. Searches that include a text query are relevance-ranked and ignore this option."
1 tool update
- Changed
off_get_product1 field changed- added
Output schema / properties / requested_fieldsAdded value: +{ + "description": "The field subset that was requested, when the caller passed `fields`. Absent means all standard fields were requested. Sections outside this subset are omitted because they were not requested — not because Open Food Facts lacks the data.", + "items": { + "description": "A field name from the requested subset.", + "type": "string" + }, + "type": "array" +}
Related MCP Connectors
Look up a packaged food by barcode, search and compare products, and check listed allergens.
Food and nutrition data: search, macros, and comparisons
Search foods, compare nutrients, and look up the full USDA FoodData Central database.
USDA FoodData Central nutrient lookup, FDA recall watch, EU FMCG labelling via remote MCP
Related MCP Servers
- AlicenseBqualityBmaintenanceEnables querying a global food nutrition database of generic and branded products, including search by name, lookup by barcode, and retrieval of resolved nutrient values pinned to a dated snapshot. Coverage spans USDA FoodData Central and Open Food Facts sources, with freshness health checks available.9AGPL 3.0
- AlicenseNot gradedqualityDmaintenanceProvides access to a comprehensive food database with 300,000+ items, enabling nutritional data lookups, food searches, and barcode scanning with all processing happening locally for privacy and speed.208MIT
- AlicenseAqualityCmaintenanceProduct evaluation MCP server for US packaged food. Health scores, ingredient safety, regulatory flags, recall history, corporate ownership.21MIT
- AlicenseNot gradedqualityCmaintenanceEnables food and nutrition lookups from Open Food Facts and USDA FoodData Central, plus local meal logging, barcode and product searches, nutrient comparisons, allergen and additive data, and daily meal totals.MIT
Glama MCP Gateway
Add one secure layer between your agents and this server.