Raw schema
{
"type": "object",
"properties": {
"query": {
"description": "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.",
"type": "string"
},
"categories_tag": {
"description": "Canonical category tag ID. Example: \"en:breakfast-cereals\", \"en:cheeses\". Use off_browse_taxonomy with facet=\"categories\" to discover valid values.",
"type": "string"
},
"brands_tag": {
"description": "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.",
"type": "string"
},
"labels_tag": {
"description": "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\".",
"anyOf": [
{
"type": "string",
"description": "One canonical label tag ID."
},
{
"maxItems": 10,
"type": "array",
"items": {
"type": "string",
"description": "One canonical label tag ID."
},
"description": "Up to 10 canonical label tag IDs, all of which must apply."
}
]
},
"allergens_tag": {
"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. To leave an allergen out, use exclude_allergens.",
"type": "string"
},
"traces_tag": {
"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"
},
"exclude_allergens": {
"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.",
"maxItems": 14,
"type": "array",
"items": {
"type": "string",
"description": "One canonical allergen tag ID to exclude, e.g. \"en:nuts\"."
}
},
"exclude_traces": {
"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.",
"maxItems": 14,
"type": "array",
"items": {
"type": "string",
"description": "One canonical allergen tag ID to exclude as a trace, e.g. \"en:nuts\"."
}
},
"ingredients_analysis_tag": {
"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.",
"type": "string",
"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"
]
},
"additives_tag": {
"description": "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.",
"type": "string"
},
"nutrition_grade": {
"description": "Filter by Nutri-Score grade. \"a\" is highest nutritional quality, \"e\" is lowest. Products without a score are excluded.",
"type": "string",
"enum": [
"a",
"b",
"c",
"d",
"e"
]
},
"nova_group": {
"description": "Filter by NOVA food processing class. \"1\"=unprocessed/minimally processed, \"4\"=ultra-processed. Products without a NOVA score are excluded.",
"type": "string",
"enum": [
"1",
"2",
"3",
"4"
]
},
"countries_tag": {
"description": "Canonical country tag ID. Example: \"en:france\", \"en:united-states\". Filters to products sold in that country.",
"type": "string"
},
"nutrient_filters": {
"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.",
"maxItems": 18,
"type": "array",
"items": {
"type": "object",
"properties": {
"nutrient": {
"type": "string",
"enum": [
"energy-kcal",
"fat",
"saturated-fat",
"carbohydrates",
"sugars",
"fiber",
"proteins",
"salt",
"sodium"
],
"description": "Nutrient to constrain, measured per 100 g. Energy is kilocalories; every other value is grams per 100 g."
},
"operator": {
"type": "string",
"enum": [
"lt",
"lte",
"gt",
"gte"
],
"description": "Comparison against value: \"lt\" below, \"lte\" at or below, \"gt\" above, \"gte\" at or above."
},
"value": {
"type": "number",
"minimum": 0,
"description": "Threshold to compare against, in the nutrient's per-100 g unit."
}
},
"required": [
"nutrient",
"operator",
"value"
],
"description": "One numeric constraint on a per-100 g nutrient value."
}
},
"sort_by": {
"description": "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.",
"type": "string",
"enum": [
"last_modified_t",
"unique_scans_n",
"created_t",
"popularity_key"
]
},
"page": {
"default": 1,
"description": "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.",
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991
},
"page_size": {
"default": 20,
"description": "Results per page (1โ50, default 20). Keep low for initial exploration; increase for comparison workflows.",
"type": "integer",
"minimum": 1,
"maximum": 50
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}