The finding
Shopify generates AI product data — the fields answer engines and shopping agents read through its Global Catalog. The current reference documents a field named unique_selling_points, types it Array[string], and prints an example carrying 2 entries.
We read that field on 1,100 products from 545 merchant stores on 2026-08-21. On every product that carried it, it held exactly one entry. Zero exceptions.
The predecessor version of the same API documented the field as uniqueSellingPoint — singular, a bare string, sitting beside the fields that are arrays. Shopify pluralised the name, pluralised the type, printed an example with more than one entry — and the generator behind the field kept producing one.
That has a consequence for anyone scoring “AI readiness”: a completeness check that expects more than one selling point is a check no product on Shopify passes today.
Check it yourself first
Before you read our numbers, you can produce your own. The endpoint is public and keyless; the agent profile in the command is Shopify's own published sample. One command, no account:
[run this] one command · no key · no account
curl -sS -X POST https://catalog.shopify.com/api/ucp/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","method":"tools/call","id":1,"params":{"name":"search_catalog",
"arguments":{"meta":{"ucp-agent":{"profile":
"https://shopify.dev/ucp/agent-profiles/2026-04-08/valid-with-capabilities.json"}},
"catalog":{"query":"merino wool sweater","pagination":{"limit":5}}}}}'Count unique_selling_points in what comes back. We ran this exact command before publishing: 5 products came back and every one of them carried exactly one selling point. If your run comes back different, the wire has changed since the measured-on date above — tell us, and we will re-run the measurement and correct this page.
[wire] one product, as it arrived
"metadata": {
"unique_selling_points": [
"Combines breathable mesh construction with cushioned support and slip-resistant outsole for versatile athletic use."
],
"top_features": "Breathable mesh upper keeps feet cool and dry for all-day comfort\nCushioned insole provides enhanced shock absorption for long runs\n…",
"tech_specs": "Closure Type: Lace-up\nUpper Material: Mesh, Synthetic\n…"
}[wire] catalog.shopify.com/api/ucp/mcp · 2026-08-21 · one product, stored in dataset.json · the lower fields are cut at their opening lines and nothing else is changed
What we measured, and how
On 2026-08-21 we sent 22 unfiltered English shopper questions — “running shoes”, “cast iron skillet”, the things a buyer actually types — to Shopify's Global Catalog search endpoint (POST catalog.shopify.com/api/ucp/mcp, UCP version 2026-04-08), 50 results per question, under our own published agent profile and an honest user agent. That returned 1,100 unique products from 545 distinct stores. 1,086 of them carried the metadata block at all, and the field counts below are over those 1,086. The whole run cost $0.00 — the endpoint is keyless and no model is called.
10 of those 22 questions were picked to break the result rather than to confirm it — luxury handbags, car floor mats, aquarium filters, wedding dresses, hair clippers, verticals with nothing in common with the rest. They returned 500 of the 1,100 products, from 258 stores. Every one that carried the field held exactly one selling point.
Then more ways of trying to break it:
- Brand-scoped runs against 4 named stores in skincare, footwear, beverages and underwear — 39 products, all ones.
- Both agent profiles — ours and Shopify's public sample, the one in the command above. Identical results, which matters because the shape could have been negotiated per profile. It is not.
- Depth — the objection that search results are truncated and the real data sits behind the per-product lookup call. We fetched 12 products by id, from 5 stores, and compared them field by field against their search-depth values: 0 of 12 differed. That arm is one question deep and we say so rather than let the sample stand in for the corpus.
What Shopify's own documents say
The primary sources are public: the predecessor reference, the current one, and the endpoint itself.
[documents] the same field, in each place it is written down
| predecessor Catalog API | current Global Catalog | live wire, n = 1,086 | |
|---|---|---|---|
| name | uniqueSellingPoint | unique_selling_points | — |
| type | string | Array[string] | array |
| example / observed | one sentence | 2 entries | exactly one, 100% |
shopify.dev/docs/api/catalog-api/lookup · shopify.dev/docs/agents/catalog/global-catalog-extension · dataset.json
To say it plainly: the one-item shape was never hidden. The singular field is right there in the predecessor reference, one search away. What changed is the documentation, not the generator — and nothing we found announces that difference. This page exists because the difference is measurable, not because it was secret.
Shopify marks these fields Inferred, and says what that means:
“Some Global Catalog response fields are generated or enriched by Shopify, and are marked Inferred in the reference. These fields might not always be present or might vary in accuracy depending on available product data.”
“…are coming from the catalog, not from the merchant's mapping, or metafields.”
Both halves matter to a merchant: the field is Shopify's output, and its shape is Shopify's choice. Its count is not something a store owner did wrong, and today there is nothing in an admin that produces a second entry.
The distributions
[distributions] over the 1,086 products with metadata
| field | documented type | wire type | observed |
|---|---|---|---|
| unique_selling_points | Array[string] | array | exactly one on 1,086 of 1,086 |
| top_features | Array[string] | newline-joined string | 4 or 5 items, never more; 95.1% sit at 5 |
| tech_specs | Array[string] | newline-joined string | 4–19 items, median 6 — genuinely variable |
| attributes | Array[Attribute] | — | absent on 1,086 of 1,086 |
dataset.json · falsify.json · the metadata key set was always exactly the fields that arrive, and no others
More divergences ride along, checkable in the same responses. metadata.attributes is documented and never came back. And of the fields that do arrive, the longer ones are not the documented type: top_features and tech_specs come as newline-joined strings, not arrays. Code that trusts the declared type and counts .length reads one for every field it reads. We know, because an earlier pass of our own made exactly that mistake before we caught it against the wire.
Small print on the denominator: 14 of the 1,100 products carried an empty metadata block. That rate was unstable across questions — 1.3% overall, up to 10% on the worst single question — so we report it and build nothing on it.
What this does to completeness scores
“AI readiness” tools score merchants on these fields. Of 6 such scorers we checked, 1 shipped tool scores selling points at all — and any scorer that reads the current reference and sets a completeness bar above one selling point has built a check that no product on Shopify passes and no merchant action changes. The number measures Shopify's generator, not the store.
That includes us. Our own catalog coverage check reads the same field, and this measurement is why it reports the selling-point count on its own and keeps it out of the grade. A grade on a field the merchant cannot move is a grade of the platform wearing the merchant's name.
What this does not show
- One platform, one endpoint, one day. This is a snapshot of Shopify's Global Catalog on the measured-on date above, not a time series. Shopify can change the generator tomorrow — and if it does, the command above will say so before we do.
- Not a random sample of Shopify. These are the products the Catalog chose to return for 22 English questions. The honest claim is “every product the Catalog returned to us”, not “every product on Shopify”.
- We do not know why. A hard cap, a prompt constraint, a schema kept from the singular era — the wire cannot tell us which, so we will not guess.
- We did not measure whether selling points matter. Nothing here shows that a second selling point would change what an engine answers next. We have not run that experiment, so this post makes no claim about it.
Data, scripts and re-runs
Everything behind the numbers is downloadable — the parsed rows, the falsification runs, the lookup-depth responses and the scripts that produced them. Re-running the measurement costs $0.00, so the measured-on date at the top is re-stamped on every republish rather than aging quietly.
- dataset.json — the 12 core questions, parsed
- falsify.json — the 10 unrelated verticals, parsed
- lookup.json — the per-product lookup responses
- multi-results.json — the brand-scoped runs
- sample-profile.json — the run under Shopify's sample profile
- dataset.py — the script behind dataset.json
- falsify.py — the script behind falsify.json
- multi.py — the script behind multi-results.json
One note for anyone reproducing later: this measurement uses /api/ucp/mcp. Shopify's older /api/mcp endpoint retires on 2026-08-31; it is not used here, and its retirement does not affect the command above.
Found an error? Tell us, and we will correct this page and say what changed.