The SA Grocery Price API
Real shelf prices from South Africa’s big supermarkets, in clean JSON. Not a survey - the same data behind LoyaltyHub, refreshed twice a week.
Real prices
Woolworths, Checkers, Shoprite, Pick n Pay, Clicks, Dis-Chem and Makro - tens of thousands of products.
Freshness built in
Every response tells you exactly when the data was last updated.
Simple auth
One API key. Bearer token or x-api-key header. JSON everywhere.
Quickstart
Create a key below, then:
curl https://loyaltyhub.co.za/api/v1/prices?search=milk \ -H "Authorization: Bearer lh_live_your_key_here"
Endpoints
GET /api/v1/retailersRetailers we track, each with its last-updated time.GET /api/v1/indexMonthly SA Grocery Price Index (aggregated).GET /api/v1/prices?search=milkCurrent prices. Filter by barcode(s), retailer, department, category, search or changed_since.GET /api/v1/products?barcode=6001030002075One product, grouped across every retailer. Use barcodes= for up to 50 at once, or search= (ranked).GET /api/v1/history?barcode=6001030002075Price history for a product.GET /api/v1/departmentsThe department ids to filter by (e.g. dairy-eggs, beverages). No key needed.Every response includes an updated_at timestamp and your remaining monthly quota.
Pagination
List endpoints return up to limit rows (max 500, default 50). Use offset to page through the rest - the meta tells you total, has_more and the next_offset to request.
GET /api/v1/prices?search=milk&limit=500&offset=0 GET /api/v1/prices?search=milk&limit=500&offset=500 # next page
Freshness & departments
/prices and /products only return prices seen on the retailer’s site in the last 30 days, so delisted products drop out. Widen that with max_age_days (up to 365); each row’s last_seen_at says exactly when we last saw it. Every row also has a department (one fixed list across all retailers) alongside the retailer’s own category label.
GET /api/v1/prices?department=dairy-eggs&retailer=pnp GET /api/v1/prices?search=milk&max_age_days=90
Bulk lookups & incremental sync
Look up to 50 barcodes in one call with barcodes= (comma-separated) - one request, one call against your quota. To keep a copy in sync, pass changed_since (ISO timestamp) and you get only prices that changed or first appeared since then; each row’s price_changed_at tells you when.
GET /api/v1/products?barcodes=6001030002075,5000112607413 GET /api/v1/prices?changed_since=2026-09-01T00:00:00Z&limit=500
Unit prices
Rows include the pack size (normalised to kg, litres or items, parsed from the product name) and a unit_price, e.g. {"value": 18.99, "per": "l"}, so different pack sizes compare directly. Both are null when the size isn’t in the name.
Limits in every response
Each response carries X-RateLimit-Limit/-Remaining (per minute) and X-Quota-Limit/-Remaining/-Reset (per month), so your client can pace itself. A 429 also includes Retry-After.
Responses
Every endpoint returns JSON with a data payload and a meta block. The meta always tells you how fresh the data is (updated_at) and where your quota stands.
{
"data": [ { "retailer": "shoprite", "barcode": "6001030002075",
"name": "...", "price": 21.99, "in_stock": true,
"image_url": "https://.../product.jpg" } ],
"meta": { "count": 50, "total": 1746, "has_more": true,
"next_offset": 50, "updated_at": "2026-07-22T06:18:00Z",
"quota": 50000, "used": 12 }
}401No API key sent.
403Key is invalid or inactive.
429Monthly quota or per-minute rate limit reached (a Retry-After header tells you how long to wait).
Note: in_stock can be true, false, or null (unknown) where a retailer doesn’t report stock reliably. image_url links to the retailer’s own image, so it can be null or change over time. The free tier caps pages at 10 rows.
Pricing
Intro pricing while we’re in beta. Cancel anytime.
Free
Free
100 calls / month
up to 20 requests / minute
Starter
R199/mo
50 000 calls / month
up to 120 requests / minute
Business
R499/mo
500 000 calls / month
up to 600 requests / minute
Your keys
Data is provided as-is for informational use. Prices are collected from public retailer sites and may lag real shelf prices. Get in touch for bulk or enterprise access.