Skip to main content
Glama

openfoodfacts-mcp-server

Get Food Product by Barcode

off_get_product
Read-onlyIdempotent

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 100g and per serving, 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. 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.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
fieldsNoSubset 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.
barcodeYesProduct 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

TableJSON Schema
NameRequiredDescriptionDefault
errorNoPresent when the call failed. Absent on success.
barcodeNoThe 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.
productNoProduct 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_fieldsNoThe 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.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous 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."
  2. Changed2 schema fields changed
    • changedInput schema / properties / barcode / description
      Previous 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)."
    • changedInput schema / properties / barcode / pattern
      Previous value: -"^\\d{8,14}$"New value: +"^0*[1-9]\\d{3,39}$"
  3. Changed8 schema fields changed
    • changedOutput schema / properties / barcode / description
      Previous 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."
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous 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."
    • changedOutput schema / properties / product / properties / ecoscore_grade / description
      Previous 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."
    • changedOutput schema / properties / product / properties / ingredients / description
      Previous 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."
    • changedOutput schema / properties / product / properties / ingredients / items / description
      Previous value: -"A single parsed ingredient entry."New value: +"A single top-level parsed ingredient entry."
    • addedOutput schema / properties / product / properties / ingredients / items / properties / ingredients
      Added 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"
      +}
    • changedOutput schema / properties / product / properties / ingredients / items / properties / percent_estimate / description
      Previous 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."
    • changedOutput schema / properties / product / properties / nutriscore_grade / description
      Previous 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."
  4. Changed6 schema fields changed
    • changedInput schema / properties / fields / description
      Previous 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."
    • changedInput schema / properties / fields / items / enum
      Previous 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"
      +]
    • addedOutput schema / properties / product / properties / countries_tags
      Added 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"
      +}
    • addedOutput schema / properties / product / properties / ingredients_analysis_tags
      Added 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"
      +}
    • addedOutput schema / properties / product / properties / traces_tags
      Added 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"
      +}
    • changedOutput schema / properties / requested_fields / description
      Previous 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."
  5. Changed6 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
    • 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": [
      +      "barcode",
      +      "product"
      +    ]
      +  },
      +  {
      +    "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: `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"
      +}
    • removedOutput schema / required
      Removed value: -[
      -  "barcode",
      -  "product"
      -]
  6. Changed9 schema fields changed
    • changedInput schema / properties / fields / items / enum
      Previous 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"
      +]
    • removedOutput schema / properties / found
      Removed 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"
      -}
    • changedOutput schema / properties / product / description
      Previous 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."
    • addedOutput schema / properties / product / properties / nutriments / properties / additional_100g
      Added 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"
      +}
    • addedOutput schema / properties / product / properties / nutriments / properties / additional_serving
      Added 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"
      +}
    • addedOutput schema / properties / product / properties / serving_quantity
      Added 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"
      +}
    • addedOutput schema / properties / product / properties / serving_quantity_unit
      Added 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"
      +}
    • addedOutput schema / properties / product / properties / serving_size
      Added 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"
      +}
    • changedOutput schema / required
      Previous value: -[
      -  "barcode",
      -  "found"
      -]New value: +[
      +  "barcode",
      +  "product"
      +]
  7. Changed1 schema field changed
    • addedOutput schema / properties / requested_fields
      Added 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"
      +}
  8. First observed

TDQS

Score is being calculated.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.