Loyalty HubLoyaltyHub
Developers

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 }
}
401

No API key sent.

403

Key is invalid or inactive.

429

Monthly 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.

Grocery Price API - South African Supermarket Data | LoyaltyHub · Loyalty Hub