Bulk match maps your rows to canonical products. The interesting part is not the matches — it is what the API refuses to match, and why that refusal is the feature.
POST /v1/match
{ "rows": [
{ "id": "sku-1", "gtin": "885909950805" },
{ "id": "sku-2", "brand": "Ninja", "title": "Foodi 8-qt air fryer" } ] }
{ "matched": [
{ "id": "sku-1", "product_id": "prd_8Fk2xQ", "method": "gtin_exact", "confidence": 1.00 },
{ "id": "sku-2", "product_id": "prd_71bQa2", "method": "title_fuzzy", "confidence": 0.68,
"review": true } ],
"unmatched": [] }GTIN if you have it, then brand, MPN, title and any distinguishing attributes. Each extra field narrows the candidate set. A bare title is the weakest possible input and produces the weakest possible match.
It means the match is plausible but below the trust threshold. Route those to a human queue. A confidently wrong GTIN is far more expensive than an empty field, because it corrupts every join built on it afterwards.
A false positive maps to a cheaper different item and everything downstream underprices. A false negative leaves a real competitor unmatched, so you never see their move. Silent drift breaks an existing match when a seller relists — and that one announces nothing, which is why `match.degraded` exists as an event.
Catalogues move. Re-running match on your full set quarterly catches drift that accumulated quietly.