openfoodfacts-proxy
A FastAPI proxy for OpenFoodFacts
Supports:
- https://openfoodfacts.github.io/openfoodfacts-server/api/ref-v2/
- https://openfoodfacts.github.io/openfoodfacts-server/api/ref-v3/
- https://search.openfoodfacts.org/docs
Features
- Local-first product reads — serves product data from a local MongoDB cache with automatic upstream fallback
- API v2 and v3 support — compatible with both OFF API versions, including field projection
- Transparent upstream proxying — unrecognized requests are forwarded to the real OFF server
- Full and delta sync — import the complete OFF MongoDB dump or apply incremental delta updates
- Rate limiting — configurable per-endpoint rate limits (product reads, search)
- Search integration — local v2 search with upstream fallback; search-a-licious proxy support
- Taxonomy caching — periodic sync of categories, brands, countries, ingredients, and labels
- Image URL rewriting — rewrite OFF S3 image URLs through your own route/CDN
- Reference data endpoints — CGI-compatible and v2/v3 taxonomy/reference routes
- Facet browsing — serve facet-based product listings from the local cache
- CLI tooling —
serve,import-full, andimport-deltacommands
Installation
With pip:
python -m pip install openfoodfacts-proxy
With uv:
uv add openfoodfacts-proxy
How to use it
CLI
Start the proxy server:
openfoodfacts-proxy serve --host 0.0.0.0 --port 8000
Import the full OFF MongoDB dump:
openfoodfacts-proxy import-full
Apply delta updates:
openfoodfacts-proxy import-delta
Python
from openfoodfacts_proxy.app import create_app
app = create_app()
Supported endpoints
| Endpoint | Description |
|---|---|
GET /api/v2/product/{barcode} |
Product read (v2 format) with optional fields query |
GET /api/v3/product/{barcode} |
Product read (v3 format) with optional fields query |
GET /api/v2/search |
Search products with filters and pagination |
GET /{facet_type}/{facet_value}.json |
Facet-based product listings |
Configuration
All settings are configurable via environment variables with the OFF_PROXY_ prefix:
| Variable | Default | Description |
|---|---|---|
OFF_PROXY_MONGODB_URI |
mongodb://mongodb:27017 |
MongoDB connection string |
OFF_PROXY_MONGODB_DB |
openfoodfacts |
Database name |
OFF_PROXY_OFF_BASE_URL |
https://world.openfoodfacts.org |
Upstream OFF server |
OFF_PROXY_PRODUCT_RATE_LIMIT |
15 |
Max product requests per window |
OFF_PROXY_SEARCH_RATE_LIMIT |
10 |
Max search requests per window |
OFF_PROXY_RATE_LIMIT_WINDOW_SECONDS |
60 |
Rate limit window duration |
OFF_PROXY_UPSTREAM_TIMEOUT_SECONDS |
30.0 |
Timeout for upstream requests |
OFF_PROXY_STARTUP_SYNC_ENABLED |
true |
Run full sync on startup |
OFF_PROXY_SCHEDULER_ENABLED |
true |
Enable periodic delta/taxonomy sync |
OFF_PROXY_REWRITE_IMAGE_URLS |
true |
Rewrite OFF image URLs |
Docker
Dockerfile.aio builds the default all-in-one image and starts MongoDB inside the same container.
Dockerfile builds the app-only image and expects MongoDB to run in a separate container.
Build the all-in-one image:
docker build -f Dockerfile.aio -t openfoodfacts-proxy:aio .
Build the separate-DB image:
docker build -f Dockerfile -t openfoodfacts-proxy:app .
Run the default all-in-one container:
docker compose up --build
Run with a separate MongoDB container:
docker compose -f docker-compose.production.yml up --build
Image URL rewriting is configurable so OFF image URLs can be exposed through your own Cloudflare route instead of leaking S3 bucket details.
Set OFF_PROXY_REWRITE_IMAGE_URLS=false to disable rewriting completely.
The runtime settings are split into OFF_PROXY_IMAGE_ROUTE_BASE_URL, OFF_PROXY_IMAGE_BUCKET_NAME, OFF_PROXY_IMAGE_BUCKET_REGION, and OFF_PROXY_IMAGE_BUCKET_PREFIX.
With the defaults, a source URL like https://images.openfoodfacts.org/images/products/301/762/042/2003/front_en.820.400.jpg is rewritten to /images/data/301/762/042/2003/front_en.820.400.jpg.
If you want custom rewriting logic, pass your own OpenFoodFactsUrlMapper implementation into create_app(...).
OFF SDK E2E
The OFF SDK integration suite does not use a dedicated Compose file. It starts the proxy stack directly and always seeds MongoDB from the committed fixture dump plus fixture delta under tests/integration/off_sdk/fixtures/, not from the public OFF dataset.
Run it explicitly so normal test runs stay fast:
OFF_PROXY_RUN_SDK_E2E=1 uv run pytest tests/integration/test_off_sdk_proxy_e2e.py -m off_sdk_e2e
By default the test prefers a real local MongoDB process when mongod and mongosh are available, then falls back to Docker. You can force either mode explicitly:
OFF_PROXY_RUN_SDK_E2E=1 OFF_PROXY_SDK_E2E_MODE=local uv run pytest tests/integration/test_off_sdk_proxy_e2e.py -m off_sdk_e2e
OFF_PROXY_RUN_SDK_E2E=1 OFF_PROXY_SDK_E2E_MODE=docker uv run pytest tests/integration/test_off_sdk_proxy_e2e.py -m off_sdk_e2e
Local mode requires mongod and mongosh on PATH.
The committed fixtures live under tests/integration/off_sdk/fixtures/. The data/off-sdk-e2e/ directory is disposable gitignored runtime state written by the test harness.
Docs
uv run mkdocs build -f ./mkdocs.yml -d ./_build/
Update template
copier update --trust -A --vcs-ref=HEAD