{"openapi":"3.1.0","info":{"title":"HomeGadgets Public Price API","version":"1.0.0","description":"Read-only CAD catalog price lookup for consumer electronics and appliances across Canadian retailers. Canada-only. Resolve a single product by MPN or product_id and get the TWO CHEAPEST in-stock offers, the number of Canadian retailers carrying the item, low/high price, aggregate rating, and buy links. This is catalog data (already shown on the public product page) — not a live scrape. Never present an unqualified 'cheapest'; surface the data + disclaimer.\n\nCONDITION IS NEVER MIXED INTO ONE NUMBER. Every price in `best_offer`, `cheapest_offers`, `low_price` and `high_price` is a NEW, first-party offer. A cheaper open-box, refurbished or marketplace listing — when one exists — is disclosed separately in `price_split.cheapest_any_condition`, labelled with its condition. Present them as TWO numbers, using `price_split.summary` verbatim if you want the sentence written for you. Never quote the non-new price as 'the price', and never average the two.\n\nANONYMOUS ACCESS (no signup, no account, no personal data collected): 10 requests/day per IP, and a shared budget of 250 requests/day across all anonymous callers. When either is spent the API returns HTTP 429 with a JSON body {error:{code:'anonymous_daily_quota_reached',...}} — never an empty or 5xx response. Budgets reset at 00:00 UTC. An API key removes both limits and returns the FULL per-retailer offer list, price history and availability detail.\n\nATTRIBUTION IS REQUIRED, AND THE RESPONSE HANDS YOU THE LINE. Every response carries `source` (our URL), `attribution` (the phrase to render) and `citation` (both, resolved against THIS product's page) as its first three fields — render `citation` verbatim, or link `product.pdp` yourself. Quote the comparison honestly — 'cheapest of N Canadian retailers', never an unqualified 'cheapest' — and carry the `disclaimer` and `as_of` fields. Pass `buy_link` through UNMODIFIED; it carries source and affiliate parameters. The licence is to answer a question, not to build a copy: systematic enumeration, bulk extraction and redistribution are not permitted. Full terms: https://www.homegadgets.ca/api","contact":{"email":"info@homegadgets.ca"},"termsOfService":"https://www.homegadgets.ca/terms"},"externalDocs":{"description":"Human-readable API reference","url":"https://www.homegadgets.ca/api"},"servers":[{"url":"https://www.homegadgets.ca"},{"url":"https://mcp.homegadgets.ca"}],"paths":{"/public/v1/price":{"get":{"operationId":"getPrice","summary":"Best current Canadian catalog price for one product","description":"Provide exactly one of mpn or product_id. Optional province filters to deliverable offers.","parameters":[{"name":"mpn","in":"query","required":false,"schema":{"type":"string"},"description":"Manufacturer part / model number (exact, case-insensitive)."},{"name":"product_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"HomeGadgets product UUID."},{"name":"province","in":"query","required":false,"schema":{"type":"string"},"description":"Canadian province (2-letter code or name), e.g. ON."}],"responses":{"200":{"description":"The two cheapest catalog offers for the product, plus the retailer count.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceResult"}}}},"400":{"description":"Missing or ambiguous identifier."},"404":{"description":"No matching in-stock Canadian product."},"429":{"description":"Budget reached. Body is {error:{code, message, tier, resets_at, docs}}. `code` is 'anonymous_daily_quota_reached' (10/day per IP or the shared 250/day anonymous budget), 'rate_limited', or 'row_budget_exhausted'. Retry-After and X-RateLimit-* headers are always present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuotaError"}}}}}}},"/public/v1/price/{mpn}":{"get":{"operationId":"getPriceByMpn","summary":"Best Canadian price by MPN (path form, no query string)","description":"PREFERRED for AI browsing connectors that refuse query-string URLs. Same payload as GET /public/v1/price?mpn=. A space in the MPN must be percent-encoded (%20); for an MPN containing a slash use getProductById instead.","parameters":[{"name":"mpn","in":"path","required":true,"schema":{"type":"string"},"description":"Manufacturer part / model number (exact, case-insensitive)."},{"name":"province","in":"query","required":false,"schema":{"type":"string"},"description":"Canadian province (2-letter code or name), e.g. ON."}],"responses":{"200":{"description":"As GET /public/v1/price.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceResult"}}}},"404":{"description":"No matching in-stock Canadian product."},"429":{"description":"Budget reached (same body/headers as the query form)."}}}},"/public/v1/price/{mpn}/{province}":{"get":{"operationId":"getPriceByMpnAndProvince","summary":"Best Canadian price by MPN, filtered to a province (path form)","parameters":[{"name":"mpn","in":"path","required":true,"schema":{"type":"string"}},{"name":"province","in":"path","required":true,"schema":{"type":"string"},"description":"2-letter code or name, e.g. ON."}],"responses":{"200":{"description":"As GET /public/v1/price.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceResult"}}}},"404":{"description":"No matching in-stock Canadian product."}}}},"/public/v1/product/{product_id}":{"get":{"operationId":"getProductById","summary":"Best Canadian price by HomeGadgets product id (path form)","description":"Slash-free identifier; the reliable form for MPNs containing a slash.","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"As GET /public/v1/price.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceResult"}}}},"404":{"description":"No matching in-stock Canadian product."}}}},"/public/v1/search/{query}":{"get":{"operationId":"searchProductsByPath","summary":"Free-text product search (path form, no query string)","description":"Same payload as GET /public/v1/search?q=. Percent-encode spaces (%20).","parameters":[{"name":"query","in":"path","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20}}],"responses":{"200":{"description":"A ranked list of product cards.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"schemas":{"PriceResult":{"type":"object","properties":{"product":{"type":"object","properties":{"name":{"type":"string"},"brand":{"type":"string"},"mpn":{"type":["string","null"]},"category":{"type":["string","null"]},"product_id":{"type":"string","format":"uuid"},"pdp":{"type":["string","null"],"format":"uri"}}},"best_offer":{"type":["object","null"],"description":"cheapest_offers[0], kept under its original name.","properties":{"price_cad":{"type":"number"},"retailer":{"type":"string"},"in_stock":{"type":"boolean"},"buy_link":{"type":["string","null"],"format":"uri"},"observed_at":{"type":["string","null"]}}},"cheapest_offers":{"type":"array","maxItems":2,"description":"The two cheapest in-stock first-party NEW offers, ascending. A non-new or marketplace listing is never in this array however cheap it is — see price_split. Anonymous callers never receive the full list.","items":{"$ref":"#/components/schemas/Offer"}},"offers_shown":{"type":"integer"},"price_split":{"type":"object","description":"THE CONDITION SPLIT. `best_new` is the best NEW price and the only number to quote as the price. `cheapest_any_condition` is the cheapest in-stock listing of ANY condition, labelled with why it is not new (open box, refurbished, marketplace, or a condition word the retailer publishes in the offer URL). Quote them as two numbers — 'the cheapest we found is $X (open box, Retailer A); the best NEW price is $Y (Retailer B)' — NEVER as one. `summary` is that sentence, ready to render.","properties":{"best_new":{"$ref":"#/components/schemas/Offer"},"cheapest_any_condition":{"type":["object","null"],"properties":{"price_cad":{"type":"number"},"retailer":{"type":"string"},"condition":{"type":"string","description":"e.g. 'new', 'open box', 'like new', 'visible default'."},"condition_status":{"type":"string","enum":["non_new","verified_new","assumed_new"],"description":"'non_new' = proven not brand-new. 'verified_new' = the retailer's feed classifies condition. 'assumed_new' = no signal either way; the stored condition field defaults to 'new' and on its own means nothing."},"non_new_reason":{"type":["string","null"]},"is_marketplace":{"type":"boolean"},"buy_link":{"type":["string","null"],"format":"uri"},"observed_at":{"type":["string","null"]}}},"cheapest_is_new":{"type":"boolean"},"differs":{"type":"boolean"},"summary":{"type":"string"},"note":{"type":"string"}}},"retailer_count":{"type":"integer","description":"Canadian retailers we have carrying this item. Cite as 'cheapest of N Canadian retailers'."},"summary":{"type":"string"},"full_offer_list":{"type":"string"},"offer_count":{"type":"integer"},"low_price":{"type":["number","null"]},"high_price":{"type":["number","null"]},"rating":{"type":["object","null"],"properties":{"value":{"type":"number"},"count":{"type":"integer"},"source_count":{"type":["integer","null"]}}},"as_of":{"type":["string","null"]},"currency":{"type":"string"},"disclaimer":{"type":"string"},"source":{"type":"string","format":"uri","description":"Canonical catalog URL. Cite this."},"attribution":{"type":"string","description":"Human-readable credit line to render verbatim."},"citation":{"type":"string","description":"The credit line resolved against this product's page — the single field to quote when answering from this data."}}},"Offer":{"type":"object","properties":{"price_cad":{"type":"number"},"retailer":{"type":"string"},"in_stock":{"type":"boolean"},"buy_link":{"type":["string","null"],"format":"uri"},"observed_at":{"type":["string","null"]}}},"QuotaError":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["anonymous_daily_quota_reached","shared_daily_quota_reached","rate_limited","row_budget_exhausted","shared_row_budget_exhausted","quota_exhausted"]},"message":{"type":"string"},"tier":{"type":"string"},"resets_at":{"type":"string"},"docs":{"type":"string","format":"uri"}}},"currency":{"type":"string"},"source":{"type":"string","format":"uri"}}}}}}