{"openapi":"3.1.0","info":{"title":"Pinch Grocery API","version":"1.0.0","description":"Australian grocery price data. Search 49,000+ products across Woolworths, Coles, Aldi, and Harris Farm."},"servers":[{"url":"https://pinch-app.com/api"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Product":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"brand":{"type":"string","nullable":true},"retailer":{"type":"string","enum":["Woolworths","Coles","Aldi","Harris Farm"]},"category":{"type":"string"},"subcategory":{"type":"string","nullable":true},"product_categories":{"type":"string","nullable":true,"description":"JSON array of retailer-provided taxonomy labels for disambiguation (e.g. '[\"Pantry\",\"Herbs & Spices\"]')"},"size":{"type":"string","nullable":true},"price":{"type":"number"},"was_price":{"type":"number","nullable":true},"cup_price":{"type":"number","nullable":true},"cup_measure":{"type":"string","nullable":true},"is_on_special":{"type":"boolean"},"is_half_price":{"type":"boolean"},"savings_amount":{"type":"number","nullable":true},"image_url":{"type":"string","nullable":true},"barcode":{"type":"string","nullable":true},"product_type":{"type":"string","nullable":true},"universal_unit_price":{"type":"number","nullable":true},"promotion_type":{"type":"string","nullable":true},"special_type":{"type":"string","nullable":true},"offer_description":{"type":"string","nullable":true},"product_url":{"type":"string","nullable":true,"description":"Direct link to the product on the retailer's website"},"updated_at":{"type":"string","format":"date-time","description":"UTC"},"data_source":{"type":"string"},"data_freshness_days":{"type":"integer","description":"Days since this price was last confirmed at the retailer"},"is_fresh":{"type":"boolean","nullable":true,"description":"Price confirmed within the last 11 days and present at the most recent collection. On basket endpoints, null means the freshness lookup failed for that call (meta.freshness_lookup = failed): unknown, not stale"},"price_catalogue_week_start":{"type":"string","format":"date","nullable":true,"description":"Wednesday-anchored catalogue week (Australia/Sydney) the price was observed in; Coles/Woolworths only, null otherwise"},"special_may_have_ended":{"type":"boolean","nullable":true,"description":"True when an is_on_special price was observed before the most recent Wednesday catalogue changeover (Coles/Woolworths): the promotion may have finished, treat as unconfirmed. Null for retailers without a weekly catalogue cycle, and on basket endpoints also null when the freshness lookup failed (meta.freshness_lookup = failed). Only meaningful alongside is_fresh: true. /specials excludes true rows"}}}}},"paths":{"/health":{"get":{"summary":"Health check with data freshness incl. per-retailer in_current_catalogue_period (Wed changeover, Coles/Woolworths)","security":[],"operationId":"getHealth"}},"/register":{"post":{"summary":"Self-service API key registration. Step 1 of 2 when email verification is on: emails a 6-digit code and returns 202; the key is issued by /register/verify. Returns 201 with the key directly when verification is off.","security":[],"operationId":"registerApiKey"}},"/register/verify":{"post":{"summary":"Step 2 of 2: submit the emailed 6-digit code (with the same email, plus name/use_case) to receive the API key (201, shown once)","security":[],"operationId":"verifyRegistration","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","code"],"properties":{"email":{"type":"string","format":"email"},"code":{"type":"string","pattern":"^[0-9]{6}$"},"name":{"type":"string","minLength":2,"maxLength":100},"use_case":{"type":"string","maxLength":500}}}}}}}},"/products/search":{"get":{"summary":"Search products by name","operationId":"searchProducts","parameters":[{"name":"q","in":"query","required":true},{"name":"retailer","in":"query"},{"name":"category","in":"query"},{"name":"on_special","in":"query"},{"name":"sort","in":"query"},{"name":"limit","in":"query"},{"name":"offset","in":"query"}]}},"/products/{id}":{"get":{"summary":"Get product by ID","operationId":"getProduct"}},"/products/{id}/price-history":{"get":{"summary":"Price history with statistics","operationId":"getPriceHistory"}},"/specials":{"get":{"summary":"Current specials sorted by savings. Excludes specials captured before the latest Wed catalogue changeover (Coles/Woolworths) as unconfirmed; may be near-empty right after a changeover","operationId":"getSpecials"}},"/compare/{barcode}":{"get":{"summary":"Compare product across retailers by barcode","operationId":"comparePrices"}},"/categories":{"get":{"summary":"List product categories with counts","operationId":"getCategories"}},"/retailers":{"get":{"summary":"Retailer statistics","operationId":"getRetailers"}},"/account":{"get":{"summary":"Your account info, tier, rate limits, and usage","operationId":"getAccount"}},"/basket/compare":{"post":{"summary":"Batch search for multiple grocery items. Returns raw candidate products per query per retailer (no filtering or scoring on the candidate list). Each item also includes a per-retailer 'cheapest' hint: the cheapest candidate matching the query's inferred subcategory (not necessarily the lowest unit_price overall; falls back to lowest unit_price when no category signal exists). The hint is advisory; review the full candidate list.","operationId":"batchSearch","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"type":"object","required":["query"],"properties":{"query":{"type":"string","maxLength":200},"product_type":{"type":"string","enum":["fresh_produce","meat","dairy","pantry","frozen","drinks","bakery","deli"]},"category":{"type":"string"}}},"minItems":1,"maxItems":20},"candidates_per_retailer":{"type":"number","default":10}}}}}}}},"/basket/save":{"post":{"summary":"Save basket data and get a shareable URL (7-day TTL)","operationId":"saveBasket","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["basket_data","item_count","basket_total"],"properties":{"basket_data":{"type":"object"},"item_count":{"type":"integer"},"basket_total":{"type":"number"},"savings_amount":{"type":"number"},"source":{"type":"string"}}}}}}}},"/basket/{id}":{"get":{"summary":"Retrieve a shared basket by ID (public, no auth required). Returns 410 if expired, 404 if not found.","operationId":"getBasket","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}]}}},"externalDocs":{"description":"Full specification (YAML)","url":"https://github.com/pinchapp/pinch-data-platform/blob/main/openapi.yaml"}}