# Food Product Scraper: Nutrition, Allergens & Nutri-Score (`glitchbound/foodfacts-scraper`) Actor

Look up any packaged food by barcode, brand or category: ingredients, allergens, additives, nutrition per 100 g, Nutri-Score, NOVA processing group and Eco-Score. Open crowd-sourced data from Open Food Facts, no key.

- **URL**: https://apify.com/glitchbound/foodfacts-scraper.md
- **Developed by:** [Daniel Meshulam](https://apify.com/glitchbound) (community)
- **Categories:** E-commerce, Automation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 products

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Food Product Scraper: Nutrition, Allergens & Nutri-Score

Any packaged food, by barcode or by brand.

```json
{ "barcodes": ["737628064502", "3017620422003"] }
```

Open Food Facts data: 3M+ products, crowd-sourced, open licence, no API key.

### What each row gives you

| | |
|---|---|
| Identity | barcode, product name, brand, pack size, serving size |
| Ingredients | full text, ingredient count, additives (E-numbers) |
| Safety | allergens and traces, as clean tags |
| Nutrition per 100 g | energy kcal, fat, saturated fat, carbs, sugars, fibre, protein, salt, sodium |
| Scores | **Nutri-Score** A-E, **NOVA** processing group 1-4, **Eco-Score** |
| Context | categories, labels (organic, vegan, gluten-free), countries sold in, packaging |

### Two honest notes about the data

**Grades that mean "we don't know" come back as `null`.** Open Food Facts writes
`unknown` and `not-applicable` into the Nutri-Score and Eco-Score fields for
products nobody has scored. Passing those through as strings lets you sort a
column by a value that is not a value. They are emptied instead.

**Search is less reliable than barcode lookup, and that is measured, not
guessed.** Open Food Facts runs on donated non-profit infrastructure, and its
search index sheds load. Testing both paths six times each:

| | succeeded |
|---|---|
| barcode lookup | **6 / 6** |
| search | **3 / 6** (rest were 503) |

So barcode lookup is the default here, and search retries hard against the 503s
rather than pretending they don't happen. If a search comes back empty you get
an `error` row that says exactly this, instead of an empty dataset you have to
diagnose.

### Searching instead of listing barcodes

```json
{ "brand": "nutella", "maxSearchResults": 50 }
```

```json
{ "category": "Breakfast cereals", "country": "United States", "maxSearchResults": 200 }
```

Combine `brand`, `category`, `nameContains` and `country` freely.

### Use cases

- **Nutrition and health apps**: barcode scan to a full nutrition panel
- **Allergen checking**: allergens and traces as structured tags, not prose
- **Retail and category analysis**: every product in a category with its scores
- **Private-label benchmarking**: compare your product against the shelf
- **Research**: NOVA processing group across a category, at scale
- **Clean-label sourcing**: filter on additive count and label tags

### Pricing

Charged per product returned. An unknown barcode or a search that Open Food Facts
fails to serve produces an `error` row and costs **$0.00**.

### The Open Food Facts API, without the parts that break

This reads the official **Open Food Facts API v2** at
`world.openfoodfacts.org`. No key, no quota page, no sign-up. What the Actor
adds is the handling around it:

| | |
|---|---|
| Barcode lookup | `/api/v2/product/<code>.json`, the reliable path |
| Keyword and brand search | `/api/v2/search`, which returns 503 under load often enough to matter |
| Rate limiting | the project asks callers to go easy; this paces and retries rather than hammering |
| Field selection | only the fields you get are requested, so a lookup is one small response |

**Barcode lookup is the path to prefer.** Measured across the same probe set,
barcode lookups succeeded 6 of 6 while search succeeded 3 of 6, because the two
are served by different infrastructure and only one of them falls over. If you
have the EAN or UPC, use `barcodes`.

**Per product** you get name, brands, quantity, categories, ingredients,
allergens and traces, nutrition per 100 g or 100 ml, **Nutri-Score** A to E,
**NOVA** processing group 1 to 4, **Eco-Score**, packaging, labels, countries
sold in, and the image URLs.

**Filtering by country** uses the `country` input, so a UK or French or German
catalogue slice comes back on its own. Coverage is genuinely uneven by market,
which the note below is about, so filtering by country is also how you find out
what the data is like where you actually sell.

### Notes

- Open Food Facts is crowd-sourced. Coverage is excellent in France and Western Europe, good in North America, patchier elsewhere, and any single field may be missing on any product. Nothing is inferred to fill a gap.
- Data is published under the Open Database License (ODbL). Attribution is your responsibility when republishing.
- Nutrition values are per 100 g or 100 ml as published, so products are directly comparable without converting pack sizes.

### FAQ

#### Do I need an Open Food Facts API key?

No. Open Food Facts is an open, crowd-sourced database with no key and no quota.

#### Can I look up a product by barcode?

That is the primary mode, and the most reliable one: `barcodes: ["737628064502"]`.
Both EAN-13 and UPC-A codes work. Measured over six attempts each, barcode lookup
succeeded 6/6 while search succeeded 3/6, so barcode is the default for a reason.

#### What does a null Nutri-Score mean?

That nobody has scored the product. Open Food Facts writes `unknown` or
`not-applicable` into that field, and passing those through as text would let you
sort a column by a value that is not a value, so they come back as `null`
instead. A null is an absence, not a bad grade.

#### Are allergens reliable enough to depend on?

Treat them as a strong signal, not a guarantee. The data is crowd-sourced and any
field can be missing on any product. For anything safety-critical, verify against
the physical label. Nothing here is inferred to fill a gap.

#### Can I get every product in a category or brand?

Yes. `brand`, `category`, `nameContains` and `country` can be combined freely.
Be aware the search index runs on donated non-profit infrastructure and sheds
load under pressure; a search that Open Food Facts fails to serve comes back as
an `error` row saying so, and costs nothing.

#### Is nutrition data comparable across products?

Yes. Values are per 100 g or 100 ml exactly as published, so you can compare
products directly without converting pack sizes.

#### How good is coverage outside Europe?

Excellent in France and Western Europe, good in North America, patchier
elsewhere. Coverage is whatever contributors have added.

#### Can I use this data commercially?

Open Food Facts data is published under the Open Database License (ODbL).
Attribution is your responsibility when republishing.

# Actor input Schema

## `barcodes` (type: `array`):

EAN or UPC codes, one per line. This is the fastest and most reliable path, because barcode lookup is served separately from search and does not suffer the same outages.

## `brand` (type: `string`):

Return every product from a brand, e.g. nutella, kelloggs, danone.

## `category` (type: `string`):

An Open Food Facts English category, e.g. Breakfast cereals, Yogurts, Sodas.

## `nameContains` (type: `string`):

Free text matched against the product name.

## `country` (type: `string`):

Narrow a search to products sold in one country, e.g. France, United States, Israel.

## `maxSearchResults` (type: `integer`):

Cap on products returned by the search, not by barcode lookups.

## `proxyConfiguration` (type: `object`):

Proxy settings.

## `maxItems` (type: `integer`):

A hard ceiling on rows for the entire run, across every target. The per-target limits above cap each one separately, so fifty targets at twenty each is still a thousand rows; this caps the total. The run stops cleanly when it is reached and nothing beyond that point is fetched or charged. Leave empty for no ceiling.

## Actor input object example

```json
{
  "barcodes": [
    "737628064502",
    "3017620422003"
  ],
  "maxSearchResults": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Ingredients, allergens, additives, nutrition per 100 g, Nutri-Score, NOVA and Eco-Score.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "barcodes": [
        "737628064502",
        "3017620422003"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("glitchbound/foodfacts-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "barcodes": [
        "737628064502",
        "3017620422003",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("glitchbound/foodfacts-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "barcodes": [
    "737628064502",
    "3017620422003"
  ]
}' |
apify call glitchbound/foodfacts-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=glitchbound/foodfacts-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/vKAB65vqzDIb6KvFc/builds/0oWneE3OzANVs8TcO/openapi.json
