Fitment, expressed as products & variants
External agents shop a Shopify store through its Catalog API — and the Catalog API speaks one language: products and variants. Fitment only becomes agent-usable once it is mapped onto those records. That mapping is the work Teifi does.
Two ways to model fitment
Not every catalog handles this the same way. Two patterns dominate:
e.g. DECKED
Each year/make/model/trim combination becomes a separate product variant. Fitment is implicit in the variant title. This works with the Catalog API out of the box — agents can read the variant name and infer compatibility.
The trade-off: variant counts explode, the SKU list becomes unwieldy, and there is no structured YMM graph to query — fitment logic lives in the variant title string, not in a queryable layer.
Rack Attack × Teifi
Fitment lives in a dedicated graph — Year / Make / Model / body style / roof type — that resolves to the correct products and variants. The catalog stays clean and each record represents a real product, not a fitment row.
Agents get structured, queryable fitment plus the Catalog API's product/variant records. The trade-off: it needs a layer that maps the graph onto the Catalog API — which is what Teifi builds.
Which one fits you? It depends on catalog scale
2,048 variants/product and a small SKU count. The variant approach is workable.Rule of thumb: narrow manufacturer → variants can work; broad reseller / distributor → a fitment graph is required. Rack Attack is the latter — which is exactly why fitment lives in the graph, not the variant matrix.
How Teifi feeds the Catalog API
Teifi Parts holds the vehicle-fitment graph and resolves any Year/Make/Model/body/roof query to the exact component SKUs. Teifi Bridge keeps those components synced into Shopify as products and variants — with fitment metafields attached — so the Catalog API can serve agent-ready, fitment-aware records to any external consumer.
Kits as bundles — and why fitment isn't a variant
A rack “system” is a kit. To make it one buyable thing for the Shop app and UCP/MCP agents, each system is a real Shopify bundle product created with productBundleCreate — the parent variant is the sellable SKU, and its components (towers, fit kit, bars, lock) expand at checkout with inventory and fulfillment derived from each part. UCP and the Shop app see one product, not a pile of parts.
How many variants a bundle creates
A bundle's variants are the cartesian product of its real config options — e.g. bar color × bar length — bounded by 3 options, 30 components, and Shopify's 2,048 variants/product limit.
Example: Black/Silver × 127cm/150cm = 4 variants. Vehicle is neveran option — so the count stays tiny, and there's no practical cap on the number of bundles (one per system).
Don't encode fitment as variants
Thousands of systems × every Year / Make / Model / body / roof type = billions of combinations. A variant-per-vehicle catalog is impossible to build and pointless to maintain.
So where does fitment live? (the join question)
UCP / the Catalog API has no native vehicle-fitment field. Its filters are price, availability, shipping, condition, shop, and category (taxonomy) — there is nothing to “join” a Year/Make/Model onto, and product metafields aren't exposed for compatibility. So fitment can't (and shouldn't) ride on the product or variant.
Instead it lives in a separate fitment graph (Teifi Parts), exposed as its own tool / MCP. The agent resolves the vehicle → the set of fitting kit / product IDs first, then hands those IDs to UCP (lookup_catalog / get_product) to read price & availability and check out. The join happens at the agent layer, by product ID — not inside the catalog.
Net: bundles model the kit (a handful of variants); the fitment graph maps vehicle → kit. Two systems joined by ID — never a variant per vehicle.
Two discovery channels — and you need both
“Find a roof rack for a 2026 F-150” can reach the merchant two different ways. They're separate systems with separate rules, and fitment has to be made legible to each — neither one indexes the fitment graph for you.
1 · UCP / Catalog API — the agent-buyable channel
The catalog only exposes products, variants, and collections (+ taxonomy + ML metadata). No metaobjects, no metafields, no fitment field. So the vehicle-group primitive here is the Collection: a Roof Racks — 2026 Ford F-150 collection (generated from the fitment graph) makes a vehicle query resolve to the buyable bundles — products carry collections[] in the feed.
2 · Metaobject landing pages — the web-crawl / AEO channel
A metaobject definition with the renderable + onlineStore capabilities turns each entry into a crawlable page (/pages/<handle>/<entry>) with SEO meta — and Shopify lists them in the sitemap (SitemapResourceMetaobject). That's how Google and ChatGPT-browse find a vehicle-specific page. A vehicle metaobject → one landing page per vehicle.
The limits that shape the strategy
- · Up to 1,000,000 entries per metaobject definition — so a vehicle page per YMM is technically possible.
- · Sitemap caps at 50,000 URLs/file (Shopify auto-paginates) — not the real blocker.
- · A URL-redirect cap (
canCreateRedirects) can gate publishing that many pages. - · Crawl / index budget is the true ceiling — a million thin pages won't rank.
So the winning play is demand-prioritized, fitment-graph-driven pages + collections — not brute-forcing every combination. Collections feed UCP; renderable metaobject pages feed web crawl; the fitment graph (Teifi Parts) drives both; bundles transact. One source of truth, two channels.
Live: catalog records for a 2020 Toyota Tacoma (naked roof)
The fitment graph resolved this vehicle to the fitting rack systems; each component below is a real Shopify product variant — the unit the Catalog API serves to agents. Fetched server-side at request time via get_catalog_for_vehicle.
| Product | SKU | Price | Variant ID |
|---|---|---|---|
720501 | $359.95 | …Variant/45589023293497 | |
145107 | $159.95 | …Variant/45592208310329 | |
721520 | $144.95 | …Variant/45589023785017 | |
721500 | $144.95 | …Variant/45589025587257 | |
721520 | $144.95 | …Variant/45589023785017 | |
721500 | $144.95 | …Variant/45589025587257 | |
Thule 544 4-pack Lock Cores | — | — | not resolved |
| Product | SKU | Price | Variant ID |
|---|---|---|---|
710501 | $264.95 | …Variant/45592492539961 | |
145107 | $159.95 | …Variant/45592208310329 | |
712400 | $179.95 | …Variant/45592492900409 | |
711420 | $279.95 | …Variant/45592492736569 | |
711400 | $279.95 | …Variant/45592492703801 | |
Thule 544 4-pack Lock Cores | — | — | not resolved |
| Product | SKU | Price | Variant ID |
|---|---|---|---|
8000421 | $228.95 | …Variant/45591055401017 | |
8000425 | $269.95 | …Variant/45591057825849 | |
8000428 | $269.95 | …Variant/45591059726393 | |
8000422 | $228.95 | …Variant/45591055859769 | |
8000426 | $269.95 | …Variant/45591058382905 | |
8000429 | $269.95 | …Variant/45591060348985 | |
8000423 | $228.95 | …Variant/45591057137721 | |
Yakima 70 Inch JetStream - Black (Pair) | 8000427 | — | not resolved |
Yakima 70 Inch JetStream - Silver (Pair) | 8000430 | — | not resolved |
Yakima BaseClip 128 (Set of 2) | 8006128 | — | not resolved |
Yakima BaseClip 145 (Set of 2) | 8006145 | — | not resolved |
Yakima BaseLine Towers (with New Covers) (Set of 4) | 8000162 | — | not resolved |
Yakima 4 Pack SKS Cores | — | — | not resolved |
Every row is a real Shopify variant — the unit the Catalog API serves to agents. Fitment chose which ones apply. The gid://shopify/ProductVariant/… IDs are live GIDs from the Rack Attack catalog.
Fitment is only agent-usable once it speaks products & variants
The Catalog API is the canonical channel for agentic commerce on Shopify — and it exposes exactly one currency: product and variant records. Teifi's stack closes the gap between a vehicle fitment graph and the records agents actually consume. The live table above is the proof: a 2020 Toyota Tacoma query, resolved to real Shopify GIDs, ready for any agent to act on.