Skip to main content
Glama

openfoodfacts-mcp-server

Search Food Products

off_search_products
Read-onlyIdempotent

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.

Input Schema

NameRequiredDescriptionDefault
pageNoPage 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.
queryNoWords 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_byNoSort 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_sizeNoResults 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_tagNoBrand 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_tagNoCanonical 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_groupNoFilter by NOVA food processing class. "1"=unprocessed/minimally processed, "4"=ultra-processed. Products without a NOVA score are excluded.
traces_tagNoCanonical 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_tagNoCanonical 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_tagNoCanonical 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_tagNoCanonical country tag ID. Example: "en:france", "en:united-states". Filters to products sold in that country.
categories_tagNoCanonical category tag ID. Example: "en:breakfast-cereals", "en:cheeses". Use off_browse_taxonomy with facet="categories" to discover valid values.
exclude_tracesNoAllergen 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_gradeNoFilter by Nutri-Score grade. "a" is highest nutritional quality, "e" is lowest. Products without a score are excluded.
nutrient_filtersNoNumeric 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_allergensNoAllergen 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_tagNoVegan, 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

NameRequiredDescriptionDefault
capNoThe page_size that was applied.
pageNoCurrent page number (1-based).
errorNoPresent when the call failed. Absent on success.
shownNoNumber of products returned on this page.
totalNoMatching 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.
noticeNoGuidance 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.
omittedNoMatches 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.
productsNoMatching products. Use barcodes with off_get_product for full label data.
last_pageNoDeepest 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_sizeNoThe page_size served: the one requested, or 50 when a larger one was requested. page and last_page count pages of this size.
truncatedNoTrue when more results exist beyond this page. Absent on the final page of a result set.
page_countNoProducts 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_cappedNoPresent 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_coverageNoPresent 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_constraintsNoRows 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_snapshotNoPresent 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_boundNoTrue 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.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed14 schema fields changed
    • changedInput schema / properties / nutrient_filters / description
      Previous 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."
    • changedInput schema / properties / page_size / description
      Previous 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."
    • removedInput schema / properties / page_size / maximum
      Removed value: -10000
    • addedInput schema / properties / page_size / multipleOf
      Added value: +1
    • changedInput schema / properties / page_size / type
      Previous value: -"integer"New value: +"number"
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous 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."
    • changedOutput schema / properties / last_page / description
      Previous 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."
    • changedOutput schema / properties / notice / description
      Previous 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."
    • addedOutput schema / properties / outside_constraints
      Added 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"
      +}
    • changedOutput schema / properties / page_count / description
      Previous 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."
    • changedOutput schema / properties / products / items / properties / indexed_nutriments / description
      Previous 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."
    • changedOutput schema / properties / text_index_snapshot / description
      Previous 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."
    • changedOutput schema / properties / total / description
      Previous 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."
    • changedOutput schema / properties / total_is_lower_bound / description
      Previous 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."
  2. Changed17 schema fields changed
    • changedInput schema / properties / nutrient_filters / description
      Previous 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."
    • changedInput schema / properties / nutrient_filters / items / description
      Previous 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."
    • changedInput schema / properties / nutrient_filters / items / properties / nutrient / description
      Previous 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."
    • changedInput schema / properties / nutrient_filters / items / properties / value / description
      Previous 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."
    • changedInput schema / properties / page / description
      Previous 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."
    • changedInput schema / properties / page_size / description
      Previous 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."
    • changedInput schema / properties / page_size / maximum
      Previous value: -50New value: +10000
    • changedInput schema / properties / query / description
      Previous 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."
    • changedOutput schema / anyOf
      Previous 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"
      +    ]
      +  }
      +]
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous 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."
    • changedOutput schema / properties / last_page / description
      Previous 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."
    • changedOutput schema / properties / notice / description
      Previous 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."
    • addedOutput schema / properties / page_size
      Added 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"
      +}
    • addedOutput schema / properties / page_size_capped
      Added 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"
      +}
    • addedOutput schema / properties / products / items / properties / indexed_nutriments
      Added 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"
      +}
    • changedOutput schema / properties / text_index_snapshot / description
      Previous 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."
    • changedOutput schema / properties / truncated / description
      Previous 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. Changed1 schema field changed
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous 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."
  4. Changed18 schema fields changed
    • changedInput schema / properties / allergens_tag / description
      Previous 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."
    • changedInput schema / properties / brands_tag / description
      Previous 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."
    • addedInput schema / properties / exclude_allergens
      Added 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"
      +}
    • addedInput schema / properties / exclude_traces
      Added 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"
      +}
    • addedInput schema / properties / ingredients_analysis_tag
      Added 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"
      +}
    • addedInput schema / properties / labels_tag / anyOf
      Added 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"
      +  }
      +]
    • changedInput schema / properties / labels_tag / description
      Previous 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\"."
    • removedInput schema / properties / labels_tag / type
      Removed value: -"string"
    • changedInput schema / properties / page / description
      Previous 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."
    • changedInput schema / properties / query / description
      Previous 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."
    • addedInput schema / properties / traces_tag
      Added 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"
      +}
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous 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."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous 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"
      +]
    • addedOutput schema / properties / exclusion_coverage
      Added 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"
      +}
    • changedOutput schema / properties / last_page / description
      Previous 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."
    • addedOutput schema / properties / omitted
      Added 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"
      +}
    • changedOutput schema / properties / page_count / description
      Previous 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."
    • changedOutput schema / properties / products / items / properties / barcode / description
      Previous 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."
  5. Changed3 schema fields changed
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous 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."
    • changedOutput schema / properties / products / items / properties / ecoscore_grade / description
      Previous 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."
    • changedOutput schema / properties / products / items / properties / nutriscore_grade / description
      Previous 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."
  6. Changed7 schema fields changed
    • changedInput schema / properties / additives_tag / description
      Previous 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."
    • addedInput schema / properties / nutrient_filters
      Added 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"
      +}
    • changedInput schema / properties / query / description
      Previous 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."
    • changedInput schema / properties / sort_by / description
      Previous 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."
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous 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."
    • addedOutput schema / properties / last_page
      Added 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"
      +}
    • addedOutput schema / properties / text_index_snapshot
      Added 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"
      +}
  7. Changed7 schema fields changed
    • changedInput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • addedInput schema / additionalProperties
      Added value: +false
    • changedInput schema / properties / additives_tag / description
      Previous 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."
    • changedOutput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • addedOutput schema / anyOf
      Added value: +[
      +  {
      +    "not": {
      +      "required": [
      +        "error"
      +      ]
      +    },
      +    "required": [
      +      "total",
      +      "total_is_lower_bound",
      +      "page",
      +      "page_count",
      +      "products"
      +    ]
      +  },
      +  {
      +    "required": [
      +      "error"
      +    ]
      +  }
      +]
    • addedOutput schema / properties / error
      Added 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"
      +}
    • removedOutput schema / required
      Removed value: -[
      -  "total",
      -  "total_is_lower_bound",
      -  "page",
      -  "page_count",
      -  "products"
      -]
  8. Changed6 schema fields changed
    • addedInput schema / properties / additives_tag
      Added 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"
      +}
    • addedInput schema / properties / allergens_tag
      Added 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"
      +}
    • changedInput schema / properties / brands_tag / description
      Previous 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."
    • changedOutput schema / properties / total / description
      Previous 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."
    • addedOutput schema / properties / total_is_lower_bound
      Added 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"
      +}
    • changedOutput schema / required
      Previous value: -[
      -  "total",
      -  "page",
      -  "page_count",
      -  "products"
      -]New value: +[
      +  "total",
      +  "total_is_lower_bound",
      +  "page",
      +  "page_count",
      +  "products"
      +]
  9. Changed2 schema fields changed
    • changedInput schema / properties / page / description
      Previous 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."
    • changedOutput schema / properties / notice / description
      Previous 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."
  10. Changed2 schema fields changed
    • changedInput schema / properties / query / description
      Previous 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%\"."
    • changedInput schema / properties / sort_by / description
      Previous 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."
  11. Changed3 schema fields changed
    • addedOutput schema / properties / cap
      Added value: +{
      +  "description": "The page_size that was applied.",
      +  "type": "number"
      +}
    • addedOutput schema / properties / shown
      Added value: +{
      +  "description": "Number of products returned on this page.",
      +  "type": "number"
      +}
    • addedOutput schema / properties / truncated
      Added value: +{
      +  "description": "True when more results exist beyond this page.",
      +  "type": "boolean"
      +}
  12. Changed2 schema fields changed
    • addedInput schema / properties / sort_by
      Added value: +{
      +  "description": "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.",
      +  "enum": [
      +    "last_modified_t",
      +    "unique_scans_n",
      +    "created_t",
      +    "popularity_key"
      +  ],
      +  "type": "string"
      +}
    • addedOutput schema / properties / products / items / properties / ecoscore_grade
      Added value: +{
      +  "description": "Green-Score letter (a–e). Environmental impact indicator. Absent when not computed.",
      +  "type": "string"
      +}
  13. First observed

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.