# Food Barcode Nutrition & Allergen Lookup (`grit-77/food-barcode-nutrition`) Actor

Food barcode nutrition and allergen lookup using Open Food Facts. Export packaged-food records as JSON with source links and data-license attribution; missing products yield error rows.

- **URL**: https://apify.com/grit-77/food-barcode-nutrition.md
- **Developed by:** [Grit](https://apify.com/grit-77) (community)
- **Categories:** E-commerce, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.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.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Food Barcode Nutrition & Allergen Lookup

Turn packaged-food barcodes into structured nutrition and allergen records with Open Food Facts source links and data-license attribution.

Supply a list of food barcodes. The actor calls the official public Open Food Facts v2 product API and saves a result or an error row for each selected entry. It retrieves packaged-food composition; openFDA recalls and adverse-event records are outside this actor's scope.

### What it returns

| Field | Meaning |
|---|---|
| `barcode` | OFF-normalized barcode string; null for malformed input |
| `status` | `ok` for a usable product, otherwise `error` |
| `productName` | Product name; falls back to the source's English name |
| `brand` | Brand string as supplied by OFF; may contain multiple brands |
| `nutritionPer100g` | Ten selected nutrient values from OFF's `_100g` fields; missing values are null |
| `nutriScore` | Source Nutri-Score grade `a` through `e`, or null |
| `categoryTags` | Source category tags; language prefixes are preserved |
| `allergenTags` | Source allergen tags; null when unavailable |
| `traceTags` | Source traces / may-contain tags; null when unavailable |
| `productUrl` | Product page on Open Food Facts |
| `sourceUrl` | Product API URL including the exact fields allowlist |
| `source` | `Open Food Facts` |
| `attribution` | `Open Food Facts contributors` |
| `license`, `licenseUrl` | ODbL-1.0 database-license identifier and link |
| `contentsLicenseUrl` | Database Contents License link |
| `error`, `errorMessage` | Machine-readable error and short explanation; null on success |

Nutrition keys: `energyKcal` (kcal), `energyKj` (kJ), and `fatG`, `saturatedFatG`, `carbohydratesG`, `sugarsG`, `fiberG`, `proteinG`, `saltG`, `sodiumG` (grams). Values come from the source's standardized `_100g` fields, without estimating missing values or converting serving data. For liquids, OFF may use these fields for a 100 ml basis; check the package before comparing with solid foods. Sodium is expressed in grams, not milligrams.

Missing values are null, while a recorded zero stays zero. A returned empty allergen list means OFF reported no tags; it does **not** establish that the food is allergen-free. Check the package and manufacturer information for dietary decisions.

Dataset views cover products/allergens, nutrition, and sources/licenses. The run's key-value store also contains `DATA_LICENSE` and `RUN_STATS` records.

### Input example

The Store prefill is:

```json
{"barcodes":["3017620422003"],"maxItems":1}
```

| Input | Default / limits |
|---|---|
| `barcodes` | Required non-empty array of strings; at most 1,000 entries; accepted lengths 8, 12, 13, 14 ASCII digits |
| `maxItems` | Default 100; range 1-200; prefill 1; includes error rows |
| `concurrency` | Fixed at 1 to preserve a shared request/charging limit |

Whitespace is trimmed. Barcodes follow [OFF zero-prefix normalization](https://openfoodfacts.github.io/openfoodfacts-server/api/ref-barcode-normalization/): remove leading zeros, pad shorter codes to 8 or 13 digits as appropriate, and retain longer codes. Equivalent valid codes are deduplicated before the cap. Inputs are strings so leading zeros are not lost before processing. Check digits are not validated; a syntactically valid but absent barcode returns `not_found`.

### Output example

This is an excerpt from the real prefill run; [sample_output.json](sample_output.json) contains the full row, including category tags and source URL. Source values can change.

```json
{
  "barcode": "3017620422003",
  "status": "ok",
  "productName": "Nutella",
  "brand": "Nutella, Ferrero",
  "nutritionPer100g": {
    "energyKcal": 539.0,
    "energyKj": 2252.0,
    "fatG": 30.9,
    "saturatedFatG": 10.6,
    "carbohydratesG": 57.5,
    "sugarsG": 56.3,
    "fiberG": 0.0,
    "proteinG": 6.3,
    "saltG": 0.107,
    "sodiumG": 0.0428
  },
  "nutriScore": "e",
  "allergenTags": [
    "en:milk",
    "en:nuts",
    "en:soybeans"
  ],
  "traceTags": [],
  "productUrl": "https://world.openfoodfacts.org/product/3017620422003",
  "attribution": "Open Food Facts contributors",
  "license": "ODbL-1.0",
  "licenseUrl": "https://opendatacommons.org/licenses/odbl/1-0/"
}
```

### Pricing

Proposed pay-per-event price: **$0.002 per `product` event** ($2.00 for 1,000 successful products across appropriately sized runs). A usable product has a name or at least one supported nutrition value. Invalid inputs, duplicates, capped entries, missing products, empty records and failed lookups have no `product` charge. The actor checks the remaining event budget before processing and stops when the SDK signals the spending limit.

On 2026-10-06, the highest-user comparable found in the targeted Store searches was [fortuitous_pirate/yuka-food-score](https://apify.com/fortuitous_pirate/yuka-food-score), with 33 total users and a $0.0035 result event plus a $0.05 actor-start event. Our proposed result price is lower. Free alternatives also exist. Full prices, comparison scope, events and timestamped API receipts are in [PRICING.md](PRICING.md).

Pricing is a publication proposal and has not been configured on Apify. There is no custom startup event in this actor. The proposed platform actor-start price is zero; platform usage fees and any actual Console-configured event prices depend on the owner's publication settings. Local runs do not submit paid charges.

### Limits and source behavior

- Fixed endpoint: `https://world.openfoodfacts.org/api/v2/product/{barcode}.json`.
- Requested fields: `code,product_name,product_name_en,brands,nutriments,nutrition_grades,categories_tags,allergens_tags,traces_tags`.
- Output is explicitly projected into the field table above. Photos, contributor/user fields, revision histories and raw API response dictionaries are excluded.
- Identifying User-Agent: `FoodBarcodeNutrition/1.0 (+https://apify.com/Grit-77)`.
- One request at a time, with at least five seconds between starts, including retries. This stays below the currently documented [15 product reads/minute/IP](https://openfoodfacts.github.io/openfoodfacts-server/api/#rate-limits) for an isolated run. Avoid concurrent runs or other OFF requests on the same IP; this actor cannot coordinate other containers' traffic.
- Network failures, HTTP 429 and server errors receive at most three attempts with exponential backoff. `Retry-After` seconds and dates are respected. If a requested cooldown exceeds five minutes, the run stops sending source requests and emits uncharged `rate_limited` rows for remaining entries.
- Permanent HTTP failures and invalid source responses become per-item errors. Input-level errors fail the run with a clear message. Storage and billing errors propagate so they cannot silently look successful.
- The source requests bulk exports for larger datasets. This actor caps each run at 200 entries and does no search, crawling or enumeration. OFF currently marks v2 deprecated but supported; this integration uses v2 as specified.

### Attribution and database reuse

Contains information from [Open Food Facts](https://world.openfoodfacts.org), contributed by Open Food Facts contributors. The database is available under [ODbL 1.0](https://opendatacommons.org/licenses/odbl/1-0/) and its individual contents under the [Database Contents License](https://opendatacommons.org/licenses/dbcl/1-0/). Keep attribution and license links with exports and visible products that use this data.

When publicly redistributing the database or publicly using a derived database, preserve the ODbL notices and applicable share-alike terms. Under ODbL section 4.6, provide the machine-readable derived database or alterations as required; internet access to that copy must be free of charge. Combining or transforming records does not remove these obligations. Charging for the lookup service does not grant exclusive rights over OFF data. See [OFF's license guidance](https://openfoodfacts.github.io/openfoodfacts-server/api/tutorials/license-be-on-the-legal-side/) and the full license for details. The notices also accompany rows, fixture provenance and the `DATA_LICENSE` record.

### FAQ

**Can I use this for recalls or adverse events?** This actor covers packaged-food identity, nutrition and allergen tags. Use an appropriate openFDA integration for recall or adverse-event research.

**Does it guarantee allergen safety?** No. OFF data is community maintained and may be incomplete, inaccurate or outdated. Missing tags are unknown; they must not be turned into an allergen-free claim by downstream applications.

**Why do barcodes sometimes change?** OFF normalizes UPC/EAN zero-prefix aliases. For example, UPC `737628064502` becomes `0737628064502`; both refer to one lookup.

**What happens if a product is missing?** An uncharged error row has `error: "not_found"`. A found record without a name or supported nutrition has `error: "empty_product"`.

**Is an API key needed?** OFF reads are public and use the actor's identifying User-Agent. No OFF API key or login is used. Apify-hosted execution uses the caller's normal Apify authorization.

### Use with AI agents / MCP

After the owner deploys the actor, an AI agent can run it with the input object above and read the default dataset through the output schema's `dataset` link. Keep `maxItems` small and retain `sourceUrl`, attribution and license information in downstream records. Treat null nutrition and allergen fields as unknown.

The [Apify MCP server](https://docs.apify.com/integrations/mcp) exposes eligible deployed actors as tools. Connect your MCP client to `https://mcp.apify.com`, authorize through the supported flow, and select this actor using its actual deployed identifier. This local project has not been deployed or tested through MCP.

Example agent instruction: “Look up barcode 3017620422003 with maxItems 1. Return the supplied product composition and source link. Preserve attribution and license links. Do not infer allergen safety from missing data.”

### Local development and verification

Dependencies are listed in `requirements.txt`; the provided shared venv already has them. No packages were installed into it.

Run from this actor directory using the prescribed interpreter:

```powershell
C:/GritWork/earn/.venv/Scripts/python scripts/record_tests.py unit
C:/GritWork/earn/.venv/Scripts/python scripts/record_tests.py live
C:/GritWork/earn/.venv/Scripts/python scripts/verify_local.py
apify validate-schema
```

`verify_local.py` sets `APIFY_LOCAL_STORAGE_DIR`, writes `storage/key_value_stores/default/INPUT.json`, runs `python -m src` without the Apify CLI, saves the real sample, and exercises fresh processes with repeated/different inputs and invalid/capped cases. It also produces proof storage directories and receipts under `evidence/`.

For a direct local run after setting the input file:

```powershell
$env:APIFY_LOCAL_STORAGE_DIR = Join-Path (Get-Location) 'storage'
C:/GritWork/earn/.venv/Scripts/python -m src
```

`Dockerfile` uses `apify/actor-python:3.12`. Docker/cloud builds and hosted monetization remain unverified in this local delivery. Verification receipts and publication risks are recorded in [REPORT.md](REPORT.md).

# Actor input Schema

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

EAN-8, UPC-A, EAN-13 or GTIN-14 strings (8, 12, 13 or 14 ASCII digits). Use strings to preserve leading zeros. Codes follow OFF zero-prefix normalization; equivalent codes are deduplicated. Invalid items produce uncharged error rows. At most 1000 input entries.

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

Cap on unique lookup entries, including invalid and missing-product error rows. Use the OFF bulk data exports for larger databases.

## `concurrency` (type: `integer`):

Fixed at 1. All attempts are spaced at least five seconds apart to respect OFF product-read limits. Avoid simultaneous runs sharing one IP address.

## Actor input object example

```json
{
  "barcodes": [
    "3017620422003"
  ],
  "maxItems": 1,
  "concurrency": 1
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing nutrition, Nutri-Score, category and allergen tags, product links and lookup errors.

# 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": [
        "3017620422003"
    ],
    "maxItems": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("grit-77/food-barcode-nutrition").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": ["3017620422003"],
    "maxItems": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("grit-77/food-barcode-nutrition").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).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": [
    "3017620422003"
  ],
  "maxItems": 1
}' |
apify call grit-77/food-barcode-nutrition --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,grit-77/food-barcode-nutrition"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/BVZN8QIlcbtudv8yK/builds/GdesUEPEstCa0TbET/openapi.json
