{"openapi":"3.1.0","info":{"title":"ShelfAtlas Catalog API","version":"1.0.0","description":"Read access to normalised Danish retail catalog data — products, offers, stores, and chains. Authenticate with a Bearer `sa_live_` API key. This document is the contract the official ShelfAtlas SDKs conform to."},"servers":[{"url":"https://api.shelfatlas.com/api/v1/public/catalog","description":"Production"}],"security":[{"bearerAuth":[]}],"paths":{"/products":{"get":{"operationId":"listProducts","summary":"List products","tags":["Products"],"parameters":[{"name":"chain_slug","in":"query","required":false,"schema":{"type":"string","pattern":"^[a-z0-9-]+$","minLength":1,"maxLength":64},"description":"Restrict to a single chain by slug."},{"name":"brand_id","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated list of UUIDs (1–100). Match products by brand."},{"name":"category_id","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated list of UUIDs (1–100). Match products by primary category."},{"name":"ids","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated list of UUIDs (1–100). Match products by id."},{"name":"exclude_ids","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated list of UUIDs (1–100). Exclude these product ids."},{"name":"cursor","in":"query","required":false,"schema":{"type":["string","null"],"maxLength":512},"description":"Opaque pagination cursor returned in a previous response."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"Page size (1–200, default 50)."}],"responses":{"200":{"description":"A page of products.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"additionalProperties":false,"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"pagination":{"type":"object","additionalProperties":false,"properties":{"limit":{"type":"integer","description":"The effective page size."},"cursor":{"type":["string","null"],"description":"Opaque cursor for the next page; null on the last page."}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/ChainNotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/products/{id}":{"get":{"operationId":"getProduct","summary":"Get a product by id","tags":["Products"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The product.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"additionalProperties":false,"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Product"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/products/{id}/offers":{"get":{"operationId":"listProductOffers","summary":"List offers matched to a product","tags":["Products","Offers"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"cursor","in":"query","required":false,"schema":{"type":["string","null"],"maxLength":512},"description":"Opaque pagination cursor returned in a previous response."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"Page size (1–200, default 50)."}],"responses":{"200":{"description":"A page of offers for the product.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"additionalProperties":false,"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Offer"}},"pagination":{"type":"object","additionalProperties":false,"properties":{"limit":{"type":"integer","description":"The effective page size."},"cursor":{"type":["string","null"],"description":"Opaque cursor for the next page; null on the last page."}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/offers":{"get":{"operationId":"listOffers","summary":"List offers","description":"Filter by a single product, a batch of products, or a chain. product_id / product_ids / chain_slug cannot be combined.","tags":["Offers"],"parameters":[{"name":"product_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Offers matched to a single product."},{"name":"product_ids","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated list of UUIDs (1–100). Offers matched to a batch of products."},{"name":"chain_slug","in":"query","required":false,"schema":{"type":"string","pattern":"^[a-z0-9-]+$","minLength":1,"maxLength":64},"description":"Restrict to a single chain by slug."},{"name":"cursor","in":"query","required":false,"schema":{"type":["string","null"],"maxLength":512},"description":"Opaque pagination cursor returned in a previous response."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"Page size (1–200, default 50)."}],"responses":{"200":{"description":"A page of offers.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"additionalProperties":false,"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Offer"}},"pagination":{"type":"object","additionalProperties":false,"properties":{"limit":{"type":"integer","description":"The effective page size."},"cursor":{"type":["string","null"],"description":"Opaque cursor for the next page; null on the last page."}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/ChainNotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/offers/{id}":{"get":{"operationId":"getOffer","summary":"Get an offer by id","tags":["Offers"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The offer.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"additionalProperties":false,"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Offer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/price-history":{"get":{"operationId":"getPriceHistory","summary":"Daily minimum price series for a set of products","tags":["Price history"],"parameters":[{"name":"product_ids","in":"query","required":true,"schema":{"type":"string"},"description":"Comma-separated list of UUIDs (1–50). Products to fetch history for."},{"name":"days","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":90,"default":30},"description":"Look-back window in days (1–90, default 30)."}],"responses":{"200":{"description":"Per-day minimum price points.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"additionalProperties":false,"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/PriceHistoryPoint"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/stores":{"get":{"operationId":"listStores","summary":"List stores, or find the nearest to a coordinate","description":"Provide lat+lng (optionally radius_km) for nearest-first ordering, or chain_slug to list a chain.","tags":["Stores"],"parameters":[{"name":"chain_slug","in":"query","required":false,"schema":{"type":"string","pattern":"^[a-z0-9-]+$","minLength":1,"maxLength":64},"description":"Restrict to a single chain by slug."},{"name":"lat","in":"query","required":false,"schema":{"type":"number","minimum":-90,"maximum":90},"description":"Latitude; must be paired with lng."},{"name":"lng","in":"query","required":false,"schema":{"type":"number","minimum":-180,"maximum":180},"description":"Longitude; must be paired with lat."},{"name":"radius_km","in":"query","required":false,"schema":{"type":"number","exclusiveMinimum":0,"maximum":500},"description":"Optional radius for nearest search; requires lat/lng."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"description":"Page size (1–500, default 50)."}],"responses":{"200":{"description":"Stores (nearest-first when a coordinate is given).","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"additionalProperties":false,"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Store"}},"pagination":{"type":"object","additionalProperties":false,"properties":{"limit":{"type":"integer","description":"The effective page size."},"cursor":{"type":["string","null"],"description":"Opaque cursor for the next page; null on the last page."}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/stores/{id}":{"get":{"operationId":"getStore","summary":"Get a store by id","tags":["Stores"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The store.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"additionalProperties":false,"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Store"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/chains":{"get":{"operationId":"listChains","summary":"List all chains","tags":["Chains"],"parameters":[],"responses":{"200":{"description":"All chains (not paginated).","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"additionalProperties":false,"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Chain"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/stats":{"get":{"operationId":"getStats","summary":"Platform-wide catalog aggregates (public marketing numbers)","description":"The same real numbers rendered on the ShelfAtlas homepage — live offer count, coverage, per-chain breakdown. Unauthenticated: the data is already public, so a key would protect nothing. Bounded instead by a per-IP rate limit and a CDN cache (`Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400`).","tags":["Stats"],"security":[],"parameters":[],"responses":{"200":{"description":"Current platform stats snapshot.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"additionalProperties":false,"properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Stats"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A ShelfAtlas API key: `Authorization: Bearer sa_live_<key>`."}},"schemas":{"Product":{"type":"object","additionalProperties":true,"required":["id"],"properties":{"id":{"type":"string","format":"uuid"},"canonicalName":{"type":["string","null"]},"variant":{"type":["string","null"]},"brandId":{"type":["string","null"],"format":"uuid"},"primaryCategoryId":{"type":["string","null"],"format":"uuid"},"imageUrl":{"type":["string","null"]},"ean":{"type":["string","null"]},"unitCount":{"type":"integer"},"volumeMl":{"type":["integer","null"]},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Offer":{"type":"object","additionalProperties":true,"required":["id"],"properties":{"id":{"type":"string","format":"uuid"},"chainId":{"type":["string","null"],"format":"uuid"},"storeId":{"type":["string","null"],"format":"uuid"},"productId":{"type":["string","null"],"format":"uuid"},"rawName":{"type":"string"},"price":{"type":"string","description":"Decimal string (Postgres numeric)."},"originalPrice":{"type":["string","null"]},"unitPrice":{"type":["string","null"]},"currency":{"type":"string","minLength":3,"maxLength":3},"validFrom":{"type":"string","format":"date-time"},"validTo":{"type":"string","format":"date-time"},"imageUrl":{"type":["string","null"]},"matchedProductIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Which of the query's products this offer matched. Present only on product-scoped offer queries (product_id / product_ids / /products/:id/offers). Use this — not the legacy nullable productId column — to attribute offers to products."}}},"Store":{"type":"object","additionalProperties":false,"required":["id","name","chainSlug","chainName","address","city","lat","lng","timezone"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"chainSlug":{"type":"string"},"chainName":{"type":"string"},"address":{"type":"string"},"city":{"type":"string"},"postalCode":{"type":["string","null"]},"lat":{"type":"number"},"lng":{"type":"number"},"openingHours":{"type":["object","null"],"description":"Weekly opening hours keyed mon..sun; each an array of {open,close} \"HH:MM\" intervals (empty = closed).","additionalProperties":{"type":"array","items":{"type":"object","required":["open","close"],"properties":{"open":{"type":"string"},"close":{"type":"string"}}}}},"timezone":{"type":"string"}}},"Chain":{"type":"object","additionalProperties":false,"required":["id","slug","name","country"],"properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"name":{"type":"string"},"displayName":{"type":["string","null"]},"country":{"type":"string","minLength":2,"maxLength":2},"logoUrl":{"type":["string","null"]}}},"PriceHistoryPoint":{"type":"object","additionalProperties":false,"required":["productId","date","minPriceCents","chainSlug"],"properties":{"productId":{"type":"string","format":"uuid"},"date":{"type":"string","format":"date","description":"UTC day, YYYY-MM-DD."},"minPriceCents":{"type":"integer","description":"Minimum price that day, in øre."},"chainSlug":{"type":"string"}}},"ChainStat":{"type":"object","additionalProperties":false,"required":["name","slug","logoUrl","storeCount","liveOfferCount"],"properties":{"name":{"type":"string"},"slug":{"type":"string"},"logoUrl":{"type":["string","null"]},"storeCount":{"type":"integer"},"liveOfferCount":{"type":"integer"}}},"RecentOffer":{"type":"object","additionalProperties":false,"required":["rawName","price","currency","chainName","imageUrl"],"properties":{"rawName":{"type":"string"},"price":{"type":"string","description":"Decimal string (Postgres numeric)."},"currency":{"type":"string","minLength":3,"maxLength":3},"chainName":{"type":["string","null"]},"imageUrl":{"type":["string","null"]}}},"Stats":{"type":"object","additionalProperties":false,"required":["liveOffers","storesCount","chainsCovered","productsCount","priceHistoryDays","perChain","recentOffers","generatedAt"],"properties":{"liveOffers":{"type":"integer"},"storesCount":{"type":"integer"},"chainsCovered":{"type":"integer"},"productsCount":{"type":"integer"},"priceHistoryDays":{"type":"integer","description":"Age of the oldest price observation, in days."},"perChain":{"type":"array","items":{"$ref":"#/components/schemas/ChainStat"}},"recentOffers":{"type":"array","items":{"$ref":"#/components/schemas/RecentOffer"}},"generatedAt":{"type":"string","format":"date-time","description":"Wallclock when this snapshot was assembled."}}},"Error":{"type":"object","additionalProperties":true,"required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code."},"message":{"type":"string"},"detail":{"type":"string"},"retry_after_sec":{"type":"integer","description":"Seconds to wait before retrying (429)."},"limit":{"type":"integer"},"upgrade_url":{"type":"string","format":"uri"}}}},"responses":{"BadRequest":{"description":"Invalid parameters (`invalid_params`, `incompatible_params`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing, invalid, or revoked API key (`missing_api_key` / `invalid_api_key` / `revoked_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ChainNotFound":{"description":"The requested chain_slug does not exist (`chain_not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit or monthly limit reached (`rate_limited` / `monthly_limit_reached`).","headers":{"Retry-After":{"description":"Seconds to wait before retrying (present on `rate_limited`).","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ServiceUnavailable":{"description":"Temporary backend failure (`service_unavailable`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}