API / Decisioning & equivalence
Find what else does this job, with every meaningful difference enumerated and priced. Not 'similar products' — alternatives with the trade-offs made explicit.
POST /v1/substitutes
| Name | Type | Notes |
|---|---|---|
| ref | string | The product to substitute for. |
| must_keep | string[] | Attributes that may not regress. |
| budget_minor | int | Optional ceiling on landed cost. |
POST /v1/substitutes
{ "ref": "prd_8Fk2xQ", "must_keep": ["anc", "wireless"], "budget_minor": 20000 }
→ 200
{ "substitutes": [
{ "product_id": "prd_4a91xB", "title": "Bose QuietComfort Ultra",
"relation": "substitute", "confidence": 0.93,
"best": { "seller": "amazon", "price": "$249.00" },
"price_delta_minor": 5100,
"better": [ { "attribute": "comfort_rating", "delta": "+0.4" } ],
"worse": [ { "attribute": "battery_hours", "a": 30, "b": 24 } ],
"same": ["anc", "wireless", "multipoint"] },
{ "product_id": "prd_77kk2C", "title": "Sennheiser Accentum Plus",
"relation": "substitute", "confidence": 0.81,
"best": { "seller": "walmart", "price": "$169.99" },
"price_delta_minor": -2801,
"better": [ { "attribute": "battery_hours", "a": 30, "b": 50 } ],
"worse": [ { "attribute": "anc_depth", "note": "measurably weaker below 200Hz" } ] } ] }`better`, `worse` and `same` are returned separately rather than as a single similarity score. A score tells an agent how close something is; these tell it what it would be giving up, which is the only form a user can accept or reject.
`must_keep` is a hard filter, not a preference. If a buyer needs noise cancelling, a cheaper alternative without it is not a substitute at any price.