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.

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 fieldTypeNotes
emailstringRequired. Where the key is registered.
namestringOptional. 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
    }
  }
}
FieldMeaning
sync_scoreInteger, 0 to 100. Higher is cleaner. Objective and category-relative.
bandOne of excellent, good, fair, poor. See the bands table below.
summaryA one-sentence read on the product.
analyzed_atWhen the product was last analyzed, ISO 8601, UTC.
breakdown.basisHow the score was anchored: data-anchored where nutrition data exists, model-assessed where it does not, always relative to the product’s category.
breakdown.pillarsNamed subscores, each 0 to 100, behind the composite.
breakdown.ingredientsCounts of ingredients rated clean, worth watching, or avoid.
breakdown.flags.recall_on_recordTrue when a recall exists on record for this product.
breakdown.flags.empty_claimsHow many marketing claims the BS Detector flagged as having no enforceable meaning.
breakdown.flags.live_researchTrue 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 paramTypeNotes
categorystringOptional. Filter to one category, for example Snacks.
min_scoreintegerOptional. Only products at or above this score.
limitintegerDefault 25, maximum 100.
offsetintegerDefault 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"

200count 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.

BandScoreRead
excellent80+A clean pick within its category.
good60–79A solid choice with minor trade-offs.
fair40–59Middle of the aisle.
poorUnder 40A 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."
  }
}
StatusCodeWhen
400bad_requestThe request body or a parameter is malformed.
401invalid_keyThe x-api-key header is missing or does not match an active key.
404not_foundNo SYNC’D member has scanned that barcode yet.
409email_existsThat email already has a key.
429rate_limitedThe 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