The Sync API
Version 1.0.0 — free tier, 1,000 requests a day
Sync Scores as a source of truth for health and wellness products. One request returns a product’s 0 to 100 score, its band, and the breakdown behind it — the same objective, category-relative, never-sponsored numbers the app shows, produced by the public methodology. Scores exist for every product a SYNC’D member has scanned, and the catalog grows with every scan.
All paths below are relative to the base URL:
https://hcrxbcrqbktgqsqgfkma.supabase.co/functions/v1/api
Get a key
Keys are self-serve and free. One key per email address.
This key is shown once. Store it now — it cannot be displayed again.
Prefer the terminal? The same call:
curl -X POST https://hcrxbcrqbktgqsqgfkma.supabase.co/functions/v1/api/v1/keys \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","name":"My app"}'
Returns 201 with your key, or 409 if the email already has
one. Keys look like sk_live_ followed by 32 hex characters.
Authentication
Send your key in the x-api-key header on every request. Only two calls
work without a key: POST /v1/keys and GET /v1/health.
curl https://hcrxbcrqbktgqsqgfkma.supabase.co/functions/v1/api/v1/stats \
-H "x-api-key: sk_live_4f2a9c81d7e35b06a1c8f39e572d40bb"
Endpoints
GET /v1/health
Liveness check. No key required.
curl https://hcrxbcrqbktgqsqgfkma.supabase.co/functions/v1/api/v1/health
{"ok": true, "version": "1.0.0"}
POST /v1/keys
Create an API key. No key required. One key per email — a second request with the
same email returns 409.
| Body field | Type | Notes |
|---|---|---|
email | string | Required. Where the key is registered. |
name | string | Optional. The app or project the key is for. |
curl -X POST https://hcrxbcrqbktgqsqgfkma.supabase.co/functions/v1/api/v1/keys \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","name":"My app"}'
201 — the key, shown this once:
{
"key": "sk_live_4f2a9c81d7e35b06a1c8f39e572d40bb",
"tier": "free",
"daily_limit": 1000
}
GET /v1/products/{barcode}
Look up one product by barcode (UPC or EAN digits) and get its Sync Score with the full breakdown.
curl https://hcrxbcrqbktgqsqgfkma.supabase.co/functions/v1/api/v1/products/051500255162 \
-H "x-api-key: sk_live_4f2a9c81d7e35b06a1c8f39e572d40bb"
200 — a real response from the live catalog:
{
"barcode": "051500255162",
"name": "Creamy Peanut Butter",
"brand": "Jif",
"category": "Snacks",
"image_url": "https://images.openfoodfacts.org/images/products/005/150/025/5162/front_en.197.400.jpg",
"sync_score": 50,
"band": "fair",
"summary": "A classic, shelf-stable staple that prioritizes texture and taste over ingredient simplicity. While it provides good protein, the inclusion of added sugars and hydrogenated oils classifies it as an ultra-processed food.",
"analyzed_at": "2026-07-10T15:26:53.96488+00:00",
"breakdown": {
"basis": "Anchored to nutrition 40/100 · NOVA 4 · 1 additive, none flagged · relative to Snacks",
"pillars": [
{ "name": "Nutrition", "score": 40 },
{ "name": "Processing", "score": 22 },
{ "name": "Additives", "score": 95 }
],
"ingredients": { "clean": 3, "watch": 2, "avoid": 0 },
"flags": {
"recall_on_record": false,
"empty_claims": 2,
"live_research": true
}
}
}
| Field | Meaning |
|---|---|
sync_score | Integer, 0 to 100. Higher is cleaner. Objective and category-relative. |
band | One of excellent, good, fair, poor. See the bands table below. |
summary | A one-sentence read on the product. |
analyzed_at | When the product was last analyzed, ISO 8601, UTC. |
breakdown.basis | How the score was anchored: data-anchored where nutrition data exists, model-assessed where it does not, always relative to the product’s category. |
breakdown.pillars | Named subscores, each 0 to 100, behind the composite. |
breakdown.ingredients | Counts of ingredients rated clean, worth watching, or avoid. |
breakdown.flags.recall_on_record | True when a recall exists on record for this product. |
breakdown.flags.empty_claims | How many marketing claims the BS Detector flagged as having no enforceable meaning. |
breakdown.flags.live_research | True when live recall and lawsuit research backs this analysis. |
404 — the product is not in the catalog yet:
{
"error": {
"code": "not_found",
"message": "That barcode has not been scanned yet. Scores appear once a SYNC'D member scans the product."
}
}
GET /v1/products
List scored products. Filter by category and minimum score; page with
limit and offset. Items are the product summary without
breakdown.
| Query param | Type | Notes |
|---|---|---|
category | string | Optional. Filter to one category, for example Snacks. |
min_score | integer | Optional. Only products at or above this score. |
limit | integer | Default 25, maximum 100. |
offset | integer | Default 0. Skip this many results for paging. |
curl "https://hcrxbcrqbktgqsqgfkma.supabase.co/functions/v1/api/v1/products?min_score=80&limit=2" \
-H "x-api-key: sk_live_4f2a9c81d7e35b06a1c8f39e572d40bb"
200 — count is the total number of products matching the
query (for paging). A real response from the live catalog:
{
"count": 4,
"items": [
{
"barcode": "00817806",
"name": "Wild Boreal Blueberries",
"brand": "Trader Joe's",
"category": "Snacks",
"image_url": "https://images.openfoodfacts.org/images/products/000/000/081/7806/front_en.15.400.jpg",
"sync_score": 96,
"band": "excellent",
"analyzed_at": "2026-07-12T20:42:16.925081+00:00"
},
{
"barcode": "00161411",
"name": "Organic Wild Blueberries",
"brand": "Trader Joe's",
"category": "Snacks",
"image_url": "https://images.openfoodfacts.org/images/products/000/000/016/1411/front_en.16.400.jpg",
"sync_score": 96,
"band": "excellent",
"analyzed_at": "2026-07-12T20:42:13.156028+00:00"
}
]
}
GET /v1/stats
A snapshot of the scored catalog: how many products carry a score, the average, and the band distribution.
curl https://hcrxbcrqbktgqsqgfkma.supabase.co/functions/v1/api/v1/stats \
-H "x-api-key: sk_live_4f2a9c81d7e35b06a1c8f39e572d40bb"
200 — the catalog is young and growing with every member scan; this is
the real snapshot at the time these docs were written:
{
"products": 19,
"average_score": 58,
"bands": {
"excellent": 4,
"good": 2,
"fair": 12,
"poor": 1
}
}
Score bands
Scores are objective and category-relative. Certifications never change a score, and no brand can pay for one — the methodology is public.
| Band | Score | Read |
|---|---|---|
excellent | 80+ | A clean pick within its category. |
good | 60–79 | A solid choice with minor trade-offs. |
fair | 40–59 | Middle of the aisle. |
poor | Under 40 | A cleaner pick almost always exists. |
Rate limits
The free tier includes 1,000 requests a day per key. Every keyed response carries the current budget in headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 997
When the day’s budget is spent, requests return 429 rate_limited until it
renews.
Errors
Every error uses the same shape: an error object with a machine-readable
code and a human-readable message.
{
"error": {
"code": "invalid_key",
"message": "That API key is not recognized."
}
}
| Status | Code | When |
|---|---|---|
| 400 | bad_request | The request body or a parameter is malformed. |
| 401 | invalid_key | The x-api-key header is missing or does not match an active key. |
| 404 | not_found | No SYNC’D member has scanned that barcode yet. |
| 409 | email_exists | That email already has a key. |
| 429 | rate_limited | The key has spent its daily request budget. |
Building something that needs more than the free tier, or hit something that looks wrong? madison@syncdbymadison.com