Skip to content
Scores are never sold.How it works

Developers · read-only

Registry API.

Everything the registry publishes — store profiles, trust scores with per-component breakdowns, catalogs and daily history — is available as JSON, CSV, RSS and SVG. No keys, no signup, CORS open. Published under CC BY 4.0 — reuse it, including commercially, and credit the registry with a link to the store profile.

The data itself — field dictionary, cadence, limits, citation: /dataset · Score methodology: /how-we-score · machine summary for AI crawlers: /llms.txt

GET/api

API index: lists the available endpoints, their parameters and the usage terms.

endpoints.registry.params
accepted query params for /api/registry: page (integer, default 1) and niche (beauty-skincare | fashion | supplements | home-kitchen | food-drink).
id
the store slug, the key /api/store takes. A slug can change — a rename, or a duplicate merged into another record: the old profile URL then answers with a permanent redirect, but /api/store/{old slug} returns 404. Store the domain beside the slug so you can find the record again.
code
display label only (BTY-0042). The prefix follows the niche, so it changes if a store is recategorised — never key on it.
terms
CC BY 4.0 for the registry’s own data — scores, facts, audit text — free to use, including commercially, with attribution: a link to the store profile. Product names, images and descriptions belong to the stores and are not covered (terms, section 3).
rate limits
Read endpoints are unmetered; they are edge-cached, so polling them costs you latency rather than access. /api/suggest is the exception — it runs a live query per keystroke and is capped per IP, returning 429 with Retry-After: 10 when you exceed it. Honour Retry-After rather than retrying immediately; there is no key to raise, and no limit that a paid plan would lift, because there are no plans.
stability
Field names and shapes are additive: new fields may appear, existing ones are not renamed or removed without a version note. The scoring formula is versioned separately: every history row carries rules_version, while the top-level trust_score has no version field: it is the latest nightly score, computed under the current rules — v0.10 today. What each version changed is listed on /changelog.

shape example (truncated)

{
  "name": "StoreProfiles Registry API",
  "description": "Public, read-only access to the independent registry…",
  "endpoints": {
    "registry": {
      "url": "https://storeprofiles.com/api/registry",
      "params": {
        "page": "integer, default 1 (50 per page)",
        "niche": "beauty-skincare | fashion | supplements | home-kitchen | food-drink"
      }
    },
    "store": { "url": "https://storeprofiles.com/api/store/{slug}" }
  },
  "terms": "CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/) for the registry’s own data — scores, facts, audit text: …"
}

GET/api/registry

Paginated list of every store on record, 50 per page, ranked by trust score. Filters: ?page=2, ?niche=fashion.

meta
total (count of live stores), page, per_page (50), niche (null when unfiltered).
stores[].code
registry record code — niche prefix + id, e.g. BTY-0042. Display-only and not stable: it changes when a store is recategorised, and /api/store says so with code_is_stable: false.
stores[].claimed
true once the owner proved domain control — a meta tag on the homepage, a file at /.well-known/storeprofiles-verify.txt or a DNS TXT record. Adds +15 to the score (rules v0.10); the owner can then edit the name, description, social links and partner terms, never the score or the audit text.
stores[].trust_score
integer 0–100, computed from the store's public data under the published formula. Null while the first audit is in preparation.
stores[].product_count
the store's real catalog size from walking its whole feed (a lower bound when catalog_truncated is true); products_sampled is how many of those products we store.
stores[].profile / api
canonical URLs of the human profile page and this store's JSON endpoint.

shape example (truncated)

{
  "meta": { "total": 3081, "page": 1, "per_page": 50, "niche": null },
  "stores": [
    {
      "code": "BTY-0042",
      "slug": "example-beauty",
      "name": "Example Beauty Co.",
      "domain": "examplebeauty.com",
      "niche": "beauty-skincare",
      "claimed": false,
      "trust_score": 71,
      "product_count": 2011,
      "catalog_truncated": false,
      "products_sampled": 250,
      "profile": "https://storeprofiles.com/store/example-beauty",
      "api": "https://storeprofiles.com/api/store/example-beauty",
      "updated_at": "2026-07-18T04:12:09.000Z"
    }
  ]
}

GET/api/store/{slug}

One store in full: profile, score breakdown, catalog stats, the rule-based audit, up to 12 products and daily history. 404 { "error": "not_on_record" } for unknown or dead stores, and for a slug the store no longer uses.

code / code_is_stable
the display label and a reminder that it is not a key: code_is_stable is always false.
verdict
a ready-to-quote sentence answering "is {store} legit": it opens with "Yes" or "On record" depending on the trust score, then the score, the strongest pro and the main caveat, computed from public data. Null before the first score.
niche_percentile
the store's standing among audited peers in its niche, 0–100 ("higher than N% of niche stores"). Null when the niche has fewer than 8 scored stores.
score_breakdown
the trust-score receipt, one {got, max} pair per component: on_record (5), catalog_depth (≤25), images (≤15), descriptions (≤12), pricing_data (≤8), claimed (15) plus verified_signals (≤20), the verified tier from collected external facts (policies, reviews, speed). The maxima sum to exactly 100 and the components sum to trust_score — no separate cap since rules v0.7, and since v0.8 verified_signals is only added when the catalog clears a quality bar. Same formula as /how-we-score.
catalog
product_count is the store's real catalog size, counted by walking every page of its public feed — not the number of rows we keep. We store up to 250 products from each crawl (rows the feed has not returned for 14 days are dropped), so min_price (the lowest price above zero — the CSV's starts at 2), avg_price and currency are computed from that sample and products_sampled gives you its size. catalog_truncated: true means our crawl hit the feed's page cap and product_count is a lower bound.
review
the store's audit — today regenerated nightly by deterministic rules, not by an AI model. model names the rule set (its version is separate from the score's rules_version), version is 0 for rule-based audits and the endpoint returns the highest on record, then pros/cons (each point cites its source), who_for, created_at. Null before the first audit.
owner_response
the owner's public reply to the audit ({text, at}), or null. Replying never edits the audit itself.
top_products
up to 12 products: title, price, currency, image_url, url, product_type.
history
daily snapshots (up to 365): day (YYYY-MM-DD), product_count (rows we stored for the store that day: up to 250 per crawl, plus rows kept for up to 14 days after the feed stopped returning them), catalog_total (the store's real catalog that day — null before 5 August 2026, when the column started), avg_price, min_price, trust_score, rules_version — chart-ready trend data. Compare trust_score only across snapshots sharing a rules_version: a formula change is our step, not the store's.
partner_program
{active: false}, or {active: true, commission_pct, notes} once the owner enabled it in the dashboard.

shape example (truncated)

{
  "code": "BTY-0042",
  "code_is_stable": false,
  "id": "example-beauty",
  "slug": "example-beauty",
  "name": "Example Beauty Co.",
  "domain": "examplebeauty.com",
  "niche": "beauty-skincare",
  "claimed": false,
  "profile": "https://storeprofiles.com/store/example-beauty",
  "badge": "https://storeprofiles.com/badge/example-beauty",
  "trust_score": 71,
  "verdict": "Is Example Beauty Co. legit? On record — independent trust score 71/100, computed from public data, not paid reviews. Strongest signal: …",
  "niche_percentile": 74,
  "score_breakdown": {
    "on_record":         { "got": 5, "max": 5 },
    "catalog_depth":     { "got": 25, "max": 25 },
    "images":            { "got": 14, "max": 15 },
    "descriptions":      { "got": 11, "max": 12 },
    "pricing_data":      { "got": 8, "max": 8 },
    "claimed":           { "got": 0, "max": 15 },
    "verified_signals":  { "got": 8, "max": 20 }
  },
  "catalog": {
    "product_count": 2011,
    "catalog_truncated": false,
    "products_sampled": 250,
    "min_price": "9.00",
    "avg_price": "24.85",
    "currency": "USD"
  },
  "review": {
    "model": "rules-v0.12",
    "version": 0,
    "pros": [{ "point": "…", "source": "products.json" }],
    "cons": [{ "point": "…", "source": "products.json" }],
    "who_for": "…",
    "created_at": "2026-07-15T02:00:11.000Z"
  },
  "owner_response": null,
  "top_products": [
    {
      "title": "Vitamin C Serum",
      "price": "24.00",
      "currency": "USD",
      "image_url": "https://cdn.shopify.com/…/serum.jpg",
      "url": "https://examplebeauty.com/products/vitamin-c-serum",
      "product_type": "Serum"
    }
  ],
  "history": [
    {
      "day": "2026-07-17",
      "product_count": 250,
      "catalog_total": 2011,
      "avg_price": "24.85",
      "min_price": "9.00",
      "trust_score": 71,
      "rules_version": "v0.10"
    }
  ],
  "partner_program": { "active": false },
  "data_updated": "2026-07-18T04:12:09.000Z"
}

GET/badge/{slug}

Embeddable SVG trust badge (200×52): "TRUST 71/100" with a score bar, or "ON RECORD" while the score is pending. Cached 24 h; 404 for stores not on record.

response
image/svg+xml — drop it into an <img> tag. Because it is cached for 24 hours, the number can trail a nightly re-score by up to a day. Wrap it in a link to the profile, as below, so readers can check the number where it is computed.

embed example

<a href="https://storeprofiles.com/store/example-beauty">
  <img src="https://storeprofiles.com/badge/example-beauty"
       width="200" height="52"
       alt="StoreProfiles trust 71/100" />
</a>

GET/feed.xml

RSS 2.0 feed of registry events: the stores most recently added, the latest score changes between two readings under the same rules version, and one weekly-digest item, so feed readers catch changes without polling the JSON API.

item (added)
title “{store} added to the registry — {score}/100”, link to the profile, a guid tied to the store, and a description with the first reading's date and the audit's first pro and con, each with its source.
item (score change)
title “{store}: trust score {before} → {after}”, a guid per store and day; only changes where both readings share a rules_version, so a formula change never shows up as a store's move.
item (weekly)
one “This week in the registry — {ISO week}” item with live counts of stores added, stores claimed and partners queued since Monday; links to /weekly.
headers
Content-Type application/rss+xml, hourly cache.

GET/registry.csv

The whole live registry as one CSV download (Content-Disposition: attachment; filename registry-YYYY-MM-DD.csv) — ready for spreadsheets, pandas or BI tools.

columns
slug, name, domain, niche, status, trust_score, products, distinct_products, min_price, currency, claimed, profile_url. One row per live store, ranked by trust score.
products / distinct_products
rows we store for the store (up to 250 per crawl, kept for 14 days after the feed stops returning them) and the distinct titles among them — not the catalog size, which the JSON API returns as product_count.
min_price / currency
entry price — the cheapest product priced at 2 or above in the store's modal (most common) currency; the currency column is that modal currency. Same rule the niche reports use, so mixed-currency catalogs never distort the number.
escaping
RFC 4180 — names containing commas, quotes or newlines arrive quoted, inner quotes doubled.

shape example (first rows)

slug,name,domain,niche,status,trust_score,products,distinct_products,min_price,currency,claimed,profile_url
example-beauty,"Example Beauty, Co.",examplebeauty.com,beauty-skincare,ingested,71,250,214,9.00,USD,false,https://storeprofiles.com/store/example-beauty

Terms of use, in plain words

  • CORS is open — every endpoint sends Access-Control-Allow-Origin: *, so browser apps can call it directly.
  • Responses are cached for an hour (the badge for 24 h). Data refreshes on the daily ingest, so polling more often than hourly buys nothing.
  • No auth, no keys — and no uptime or rate-limit promises. Be gentle; abusive traffic gets blocked without ceremony.
  • Where the data comes from: what the stores publish — product feeds, policy pages, product pages — plus our own timing probe and public records such as domain registration. Every number is checkable at its source — see /how-we-score.
  • Scores are computed, never sold. No store can pay to be listed, scored, or ranked — and the API returns the same numbers the profile pages show.