Beta endpoint
Catalog similarity API
GET /v1/catalog/{id}/similar returns beta similar-coffee candidates for member sessions and paid API-key integrations.
GET /v1/catalog/{id}/similar finds candidate coffees related to one catalog entry. The route is useful for likely-same-bean checks, substitution research, account-linked agents, and pricing context around comparable lots.
Matches are beta confidence candidates based on origin, processing, tasting similarity signals, and deterministic identity gates. The endpoint separates canonical candidates from similar recommendations; neither group is an accepted canonical identity.
Endpoint and access
- Anonymous callers receive 401 auth_required. The response does not leak match data.
- Signed-in viewer sessions and API Green keys receive 403 entitlement_required. Locked teasers return similar_match_count: null so denied requests do not run the expensive similarity count path or leak match rows.
- API-key callers must satisfy requiredPlan member and requiredScope catalog:read. Successful API-key responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset.
- 429 responses use the same quota envelope as /v1/catalog and include Retry-After.
- 404 means the target catalog coffee was not found after the caller has enough access to request matches.
| Route | Method | Auth | Status | Contract |
|---|---|---|---|---|
| /v1/catalog/{id}/similar | GET | Member/admin session or API key with API Origin or Enterprise and catalog:read | Beta | Returns target plus grouped beta matches, score dimensions, identity classification, blocker reasons, price_delta_1lb, pricing fallbacks, and cautious copy. |
Path and query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| id | positive integer | required | Catalog coffee ID. The route validates against the Postgres int4 ceiling of 2147483647 before any database work. |
| threshold | number between 0.5 and 0.99 | 0.7 | Minimum similarity score sent to the matching RPC. |
| limit | positive integer up to 25 | 10 | Number of matches returned after any mode filtering. |
| stocked_only | true | false | true | Whether candidate rows must currently be stocked. |
| mode | all | likely_same | similar_profile | all | Filters normalized matches after scoring. likely_same asks the server to overfetch before filtering so likely-same rows are not hidden by profile matches. |
Structured validation is part of the contract
Invalid id, threshold, limit, stocked_only, or mode values return HTTP 400 with error: Invalid query parameter and a details block containing parameter, value, and expected.
Response shape
data.target summarizes the requested coffee with origin, process, stocked state, legacy cost_lb compatibility, and canonical pricing fields.
data.groups.canonical_candidates and data.groups.similar_recommendations are the preferred grouped contract. data.matches remains as a transitional flat list. Each match includes coffee identity fields, canonical pricing, price_delta_1lb, score.average, score.dimensions.origin, score.dimensions.processing, score.dimensions.tasting, score.chunk_matches, match.category, match.classification.kind, match.classification.identity_eligibility, match.classification.blockers, match.confidence, match.beta, match.language, explanation.summary, explanation.signals, and compatibility.cost_lb.
meta.status is beta. meta.classification_version and meta.query_strategy identify the hard-gated identity contract and bounded vector retrieval path. Preserve the non-canonical identity warning in client copy.
curl "https://purveyors.io/v1/catalog/1182/similar?threshold=0.8&limit=5&mode=likely_same" \
-H "Authorization: Bearer pk_live_origin_or_enterprise_key"{
"data": {
"target": {
"id": 1182,
"name": "Ethiopia Guji Natural",
"source": "Supplier A",
"origin": "Guji",
"country": "Ethiopia",
"processing": "Natural",
"stocked": true,
"pricing": {
"price_per_lb": null,
"price_tiers": [{ "min_lbs": 1, "price": 8 }],
"cost_lb": 7.1,
"baseline_quantity_lbs": 1,
"baseline_price_per_lb": 8,
"baseline_source": "price_tiers"
}
},
"matches": [
{
"coffee": { "id": 2200, "name": "Ethiopia Guji Natural Lot B", "stocked": true },
"pricing": { "baseline_price_per_lb": 8.75, "baseline_source": "price_per_lb" },
"price_delta_1lb": { "amount": 0.75, "percent": 9.4, "currency": "USD" },
"score": {
"average": 0.92,
"dimensions": { "origin": 0.94, "processing": 0.91, "tasting": 0.87 },
"chunk_matches": 3
},
"match": {
"category": "likely_same",
"confidence": "high_beta",
"beta": true,
"language": "High beta confidence likely same coffee candidate. Review supplier details before acting."
},
"explanation": {
"summary": "Beta similarity score based on available origin, processing, and tasting embeddings.",
"signals": ["Origin similarity 0.94", "Processing similarity 0.91"]
}
}
]
},
"meta": {
"resource": "catalog-similarity",
"namespace": "/v1/catalog/{id}/similar",
"version": "v1",
"status": "beta",
"auth": { "kind": "api-key", "role": "viewer", "apiPlan": "member" },
"access": { "requiredCapability": "canUseBeanMatching", "canUseBeanMatching": true },
"query": { "threshold": 0.8, "limit": 5, "stockedOnly": true, "mode": "likely_same" }
}
}Error examples
{
"error": "Authentication required",
"message": "Similar coffee matching requires a member account or paid API tier.",
"code": "auth_required",
"requiredCapability": "canUseBeanMatching"
}{
"error": "Insufficient permissions",
"message": "Similar coffee matching is available to members and paid API tiers.",
"code": "entitlement_required",
"requiredCapability": "canUseBeanMatching",
"teaser": {
"locked": true,
"similar_match_count": null,
"beta": true
}
}{
"error": "Invalid query parameter",
"message": "Query parameter limit must use positive integer less than or equal to 25",
"details": {
"parameter": "limit",
"value": "99",
"expected": "positive integer less than or equal to 25"
}
}