A site-wide path is making promises your contract can’t keep. Here’s how to fix it with three HTTP calls.
When you publish an ai.json contract at /.well-known/ai.json, you’ve made a quiet but consequential claim: the schema definitions inside that file apply to your entire domain. Every AI agent that respects the well-known convention will assume your contract describes how to read any product on your site.
That assumption breaks the moment your catalog has more than one product class.
I ran into this in production recently. Mount-It!’s ai.json was thoughtful about TV mounts. Every additionalProperty path was defined, every applies_to taxonomy was correct, every consumer instruction was deliberate. The contract was good. But a third-party audit tool fetched a monitor arm PDP, walked the contract’s fields_of_interest list, and reported seven required fields missing. The fields weren’t missing in any real sense. They simply didn’t apply to monitor arms — a product class the contract wasn’t built for.
The audit was wrong. But the contract had given it no way to know.
The defensive responses are all wrong
Mark every conditional field optional. This dumbs down the contract for sophisticated consumers in service of placating naive ones. A required field that’s truly missing on a product where it applies is now indistinguishable from an emission gap. You’ve lost signal.
URL pattern matching. Glob /products/*tv-mount* and call it done. Theme rewrites break it. Translated routes break it. Subdomains break it. Worst, the pattern lives in the validator script rather than the contract, so contract and gate drift independently.
JSON-LD category strings per PDP. Cleaner. But you still need a parallel list of in-scope versus out-of-scope category strings, and Shopify’s product taxonomy shifts over time. Plus, consumers have to maintain that list to evaluate scope. The cost is on the audience, not the publisher.
None of these solve the real problem: your contract needs a way to tell agents which products it covers, and that signal needs to be authoritative, stable, and machine-readable without coupling to the theme or the URL structure.
Shopify already publishes the authoritative answer
Every collection on a Shopify store exposes /collections/{slug}/products.json — a paginated, public JSON feed listing the products that match that collection’s rules. No auth. No theme dependency. Updates reflect collection rules automatically. Stable across theme rewrites because the URL structure is a Shopify primitive.
That feed is exactly the artifact your contract needs.
Declaring scope by collection membership works because the relationship between a product and a collection is the same relationship your storefront filters use, your merchant taxonomy depends on, and your search reflects. You’re not inventing a new scope mechanism. You’re naming the one your site already runs on.
Mount-It!’s ai.json now opens with a scope block that points to three TV-mount collections. Any AI agent following the contract evaluates scope in three steps:
"scope": {
"in_scope_collections": [
{
"handle": "tv-mounts",
"products_json": "https://www.mount-it.com/collections/tv-mounts/products.json"
},
{ "handle": "tv-stands", "products_json": "..." },
{ "handle": "rolling-tv-carts", "products_json": "..." }
],
"consumer_instruction": "Fetch each feed; a product is in scope iff its handle appears in any feed. If absent from all three, the contract does not apply. Do NOT flag absent fields as drift, missing-data, or contract violations."
}
The protocol, in three tiers
Authoritative. Fetch the feeds, build a Set of in-scope product handles, check the current PDP’s handle for membership. Three HTTP calls for the whole site. Mount-It!’s loads 134 handles across the three feeds and resolves in under a second on warm caches.
Fast path for already-rendered PDPs. A convenience_signals.in_scope_categories list of exact JSON-LD category strings. An agent that already has the PDP HTML in hand can check the Product.category field without an extra fetch.
Fallback for naive scanners. A convenience_signals.url_patterns array. Lowest fidelity, but better than no signal when the feeds can’t be reached.
The protocol fails closed at the CI gate (treat as in-scope and validate) and fails open at the audit tool (treat as out-of-scope and skip). Opposite policies for opposite tools, both correct.
What this looks like running
Validating against a manifest of representative PDPs:
PASS fixed_wall_mount (in collection feed)
PASS tilting_wall_mount (in collection feed)
PASS full_motion_wall_mount (in collection feed)
PASS full_motion_ceiling_mount (in collection feed)
PASS motorized_ceiling_mount (in collection feed)
PASS under_cabinet_mount (in collection feed)
SKIP monitor_mount_oos (handle not in any in_scope_collections feed)
SKIP standing_desk_oos (handle not in any in_scope_collections feed)
The two SKIPs are products that should not be evaluated against the TV-mount contract. The gate skips them with explicit reasons. Third-party audits that follow the protocol will do the same.
Sub-collection inheritance works because Shopify’s collection rules cascade: every product in /collections/wall-tv-mounts already appears in /collections/tv-mounts via the parent’s rules. You don’t list sub-collections in scope. You list the parents, and the relationship is preserved by Shopify itself.
Scope is not a limitation. It’s what makes the contract honest.
Most ai.json contracts I’ve seen treat scope as implicit. The author has a particular set of products in mind, builds the schema around them, and ships the contract assuming consumers will figure out which products are covered. They won’t. Consumers will assume the contract covers everything on the domain it was published to.
A scoped contract says: here is exactly what we cover, here is how to verify, here is what we will not pretend to cover. AI agents (the sophisticated ones, which are increasingly the only ones that matter) reward that honesty with stronger recommendations within scope. Brands that don’t scope will accumulate one of two failure modes: false confidence, by claiming jurisdiction over uncovered products, or learned helplessness, by marking everything optional to defang the noise.
The collection feed is sitting there in your Shopify store right now. Public, paginated, accurate. Make it the source of truth your contract points to. Three HTTP calls. The hardest part of the design is naming the field.
FAQ
What is the scope problem in ai.json contracts?
Publishing ai.json at /.well-known/ai.json implicitly claims jurisdiction over your entire domain. Any AI agent that respects the well-known convention will assume the contract describes how to read any product on the site. That assumption breaks as soon as your catalog has more than one product class.
Why not just mark conditional fields optional?
That dumbs down the contract for sophisticated consumers to placate naive ones. A required field that’s genuinely missing on a product where it applies becomes indistinguishable from an emission gap. You lose signal across the entire contract.
Why not URL pattern matching for scope?
URL patterns couple scope to the theme and URL structure. Theme rewrites, translated routes, and subdomains all break it. The pattern usually lives in a validator script rather than the contract itself, so they drift independently over time.
Why not in-scope JSON-LD category strings?
Cleaner than URL matching, but still requires a parallel allow list of category values, and Shopify’s product taxonomy shifts over time. Consumers have to maintain the list to evaluate scope, pushing the cost onto the audience instead of the publisher.
How does scope by Shopify collection membership work?
Every collection exposes /collections/{slug}/products.json as a public JSON feed. ai.json lists the in-scope collections and their feed URLs. An AI agent fetches each feed, builds a set of in-scope product handles, and checks the current PDP’s handle for membership. Three HTTP calls cover the whole site.
How should the protocol behave on different consumer tools?
Fail closed at the CI gate (unknown handle → treat as in-scope and validate) and fail open at the audit tool (unknown handle → treat as out-of-scope and skip). Opposite policies for opposite tools, both correct.
Do I have to list every sub-collection in scope?
No. Shopify’s collection rules cascade: every product in a child collection already appears in its parent via the parent’s rules. List the parents and the relationship is preserved by Shopify itself.
Where this fits
The ai.json contract is one piece of the third layer in the GEO stack — the AI governance contract that sits on top of your Product Intelligence Layer and your structured data. For the Shopify-specific anatomy of the sibling file, see llms.txt for Shopify Plus: what to include and what to block. And for the broader argument that no app makes your store agentic-ready, only architecture does, see there is no app that makes your Shopify store agentic-ready.
If your ai.json is currently making promises it can’t keep across a mixed catalog, that’s the kind of thing the audit fixes.