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.