Pick n Pay API & Scraper
Pick n Pay is one of South Africa's largest grocery chains, and every price on its online shop belongs to one store: the same 6 x 1 L UHT milk was R97.99 at Constantia (WC21), R104.99 at two other stores and R99.99 at a fourth on the same day, and 11 of 19 products shared by four stores carried different prices.
🤖 Using an AI assistant? Copy this link into ChatGPT / Claude / Cursor — it reads every endpoint and parameter instantly and tells you if this API fits your use case.
Every call therefore reads one store, chosen with store_id (without it the site's own default store is used and the answer says so). `search` returns products for a search term with the shelf price, was-price and saving, the Smart Shopper card price kept apart from the price, multi-buy and combo deals, price per kg for meat and produce sold by weight, stock status, rating and badges, plus the category, brand, certification and price facets. `category` lists a department or aisle, `promotions` lists everything on promotion at a store, `promotion` returns every product a combo or multi-buy covers, and `product` returns one product with every promotion and its dates, stock level, barcode, brand, description, allergens, certifications, the nutrition table and the single and case variants with their own prices. `stores` lists the 438 store codes of the online shop.
What a Pick n Pay field actually contains
Fields that read differently from how they look. Every row was measured against live responses on 2026-10-15.
| Field | What it holds |
|---|---|
| store_id, store_defaulted | The store the prices belong to. "milk" counted 1,271 products at Constantia (WC21), 1,221 at GC11 and 946 at NC10; beef mince was R139.99/kg at WC21 and R119.99/kg at GG17. store_defaulted is true when you did not pass a store. |
| price, price_display | The shelf price anyone pays at that store, in rand. For meat and produce sold by weight (product ids ending _KGM) it is the price per kg ("R139.99/kg") and price_unit is "kg". |
| member_price | The Smart Shopper card price, never the shelf price: UHT milk 6 x 1 L shows price 97.99 and member_price 89.99. null when the product has no card price. |
| multi_buy | A bulk deal apart from the single price: kind multi_buy ("2 For R34.00": quantity 2, price 34.00) or combo ("Combo For R49.99": a set of different items for one price; the promotion call lists the set). card_required says whether it needs Smart Shopper. |
| promotions | Every promotion with its id, text, deal price, card requirement and start and end date. A search row carries the product's main promotion; product lists all of them (a loaf showed a combo in search and the combo plus a R17.99 Smart Shopper price on product). |
| original_price, savings | The was-price and the saving when the product is on a price reduction. null, not 0, when there is no was-price. |
| weight_options | For goods sold by weight, the pack weights you can order (for example 900 g to 5 kg, with the weight range each one may come in). |
| stock_status, stock_level | in_stock, low_stock or out_of_stock at that store; product also gives the store's stock count (844 cases of one milk at WC21). |
| rating, review_count | As each surface prints it. The search row and the product record can disagree (5.0 from 11 reviews in search, 3.18 from 5 on the product the same minute), so each is returned as printed. |
| supplier | The company that supplies or owns the brand, as Pick n Pay names it: PICK N PAY for its own brand, COCA-COLA BEVERAGES SOUTH AFRICA, CLOVER, PEPSICO SNACK BRANDS and so on. Pick n Pay sells every product itself; the brand name is on the product record. |
| variants | The pack variants of one product (single, case, pallet) with their own price and stock. Pallet units carry no price. |
Pick n Pay does not publish a price per 100 g or 100 ml on its online shop, so none is returned; goods sold by weight carry their price per kg.
Real request and response JSON
Captured from the indexed primary action, search, on .
{
"method": "POST",
"url": "https://api.reefapi.com/pnp/v1/search",
"headers": {
"x-api-key": "$REEF_KEY",
"content-type": "application/json"
},
"body": {
"query": "milk",
"store_id": "WC21"
}
}{
"ok": true,
"meta": {
"api": "pnp",
"endpoint": "search",
"mode": "live",
"latency_ms": 585.1,
"record_count": 24,
"bytes": 127748,
"cache_hit": false,
"total": 1270,
"store_id": "WC21",
"upstream_requests": 1,
"charged_credits": 2,
"version": "0.1.0",
"request_id": "9f7c8dba5f164cd5",
"queue_ms": 1.9,
"fetched_at": "2026-10-15T17:02:57.865Z"
},
"data": {
"query": "milk",
"store_id": "WC21",
"store_name": null,
"store_defaulted": false,
"total": 1270,
"total_is_capped": false,
"page": 1,
"page_size": 24,
"total_pages": 53,
"sort": "relevance",
"returned": 24,
"sponsored_count": 0,
"products": [
{
"rank": 1,
"product_id": "000000000000349246_CS",
"title": "PnP UHT Full Cream Milk 6 x 1L",
"supplier": "PICK N PAY",
"price": 97.99,
"price_display": "R97.99",
"price_type": "promotion",
"price_unit": "each",
"currency": "ZAR",
"original_price": 99.99,
"savings": 2,
"online_only_price": false,
"member_price": 89.99,
"member_price_program": "Smart Shopper",
"member_price_ends": "2026-10-18T21:59:59+0000",
"multi_buy": null,
"promotions": [
{
"promotion_id": "[trimmed-depth]",
"type": "[trimmed-depth]",
"card_required": "[trimmed-depth]",
"text": "[trimmed-depth]",
"description": "[trimmed-depth]",
"kind": "[trimmed-depth]",
"quantity": "[trimmed-depth]",
"deal_price": "[trimmed-depth]",
"starts": "[trimmed-depth]",
"ends": "[trimmed-depth]",
"validity_text": "[trimmed-depth]"
}
],
"sold_by": "unit",
"weight_options": [],
"average_weight": null,
"stock_status": "in_stock",
"in_stock": true,
"purchasable": true,
"max_order_quantity": 10,
"rating": 5,
"review_count": 5,
"badges": [
"SAVE",
"SMART SHOPPER"
],
"sponsored": false,
"image": "https://cdn-prd-02.pnp.co.za/sys-master/images/hb4/he0/81649202331678/silo-product-image-v2-13Oct2026-060106-6001007164294-Straight_on-474524-606_400Wx400H",
"url": "https://www.pnp.co.za/All-Products/Beverages/Long-Life-Milk/UHT-Milk/Full-Cream/pnp-uht-full-cream-milk-6-x-1l/p/000000000000349246_CS"
},
{
"rank": 2,
"product_id": "000000000000349246_EA",
"title": "PnP UHT Full Cream Milk 1L",
"supplier": "PICK N PAY",
"price": 17.99,
"price_display": "R17.99",
"price_type": "selling",
"price_unit": "each",
"currency": "ZAR",
"original_price": null,
"savings": null,
"online_only_price": false,
"member_price": null,
"member_price_program": null,
"member_price_ends": null,
"multi_buy": null,
"promotions": [],
"sold_by": "unit",
"weight_options": [],
"average_weight": null,
"stock_status": "in_stock",
"in_stock": true,
"purchasable": true,
"max_order_quantity": null,
"rating": null,
"review_count": 0,
"badges": [],
"sponsored": false,
"image": "https://cdn-prd-02.pnp.co.za/sys-master/images/h8f/h2e/81798209929246/silo-product-image-v2-15Oct2026-091610-6001007041052-Straight_on-19006-64_400Wx400H",
"url": "https://www.pnp.co.za/All-Products/Beverages/Long-Life-Milk/UHT-Milk/Full-Cream/pnp-uht-full-cream-milk-1l/p/000000000000349246_EA"
},
{
"rank": 3,
"product_id": "000000000000357781_EA",
"title": "PnP Full Cream Fresh Milk 2L",
"supplier": "PICK N PAY",
"price": 32.99,
"price_display": "R32.99",
"price_type": "promotion",
"price_unit": "each",
"currency": "ZAR",
"original_price": 34.99,
"savings": 2,
"online_only_price": false,
"member_price": null,
"member_price_program": null,
"member_price_ends": null,
"multi_buy": null,
"promotions": [],
"sold_by": "unit",
"weight_options": [],
"average_weight": null,
"stock_status": "in_stock",
"in_stock": true,
"purchasable": true,
"max_order_quantity": null,
"rating": null,
"review_count": 0,
"badges": [
"SAVE"
],
"sponsored": false,
"image": "https://cdn-prd-02.pnp.co.za/sys-master/images/ha6/hab/63177270558750/silo-product-image-v2-27Jun2026-060051-6001007162474-Straight_on-441005-30_400Wx400H",
"url": "https://www.pnp.co.za/All-Products/Milk%2C-Dairy-%26-Eggs/Fresh-Milk-%26-Cream/Fresh-Milk/Full-Cream/pnp-full-cream-fresh-milk-2l/p/000000000000357781_EA"
}
],
"facets": {
"categories": [
{
"name": "Milk, Dairy & Eggs",
"count": 974,
"category_id": "milk-dairy-and-eggs-423144840"
},
{
"name": "Food Cupboard",
"count": 200,
"category_id": "food-cupboard-423144840"
},
{
"name": "Beverages",
"count": 168,
"category_id": "beverages-423144840"
}
],
"certifications": [
{
"name": "Free Range",
"count": 1
},
{
"name": "Halaal",
"count": 812
},
{
"name": "Kosher",
"count": 508
}
],
"on_promotion": [
{
"name": "On Promotion",
"count": 333
}
],
"lifestyle": [
{
"name": "Dairy free",
"count": 7
},
{
"name": "Gluten Free",
"count": 64
},
{
"name": "Lactose free",
"count": 13
}
],
"brands": [
{
"name": "ALMOND",
"count": 7,
"value": "ALMOND"
},
{
"name": "AMAJOYA",
"count": 4,
"value": "AMAJOYA"
},
{
"name": "AQUAFRESH",
"count": 2,
"value": "AQUAFRESH"
}
],
"unit_of_measure": [
{
"name": "Case",
"count": 32
},
{
"name": "Single",
"count": 1174
},
{
"name": "Pack",
"count": 45
}
],
"price_ranges": [
{
"name": "R0-R49.99",
"count": 725
},
{
"name": "R50-R199.99",
"count": 503
},
{
"name": "R200-R499.99",
"count": 39
}
]
},
"breadcrumbs": []
}
}What the Pick n Pay API does
| Action | Description | Concrete use case | Key params |
|---|---|---|---|
| search | Search one Pick n Pay store's online shelf: shelf price, was-price, Smart Shopper card price (kept apart from the price), multi-buy / combo deal, price per kg for goods sold by weight, stock, rating and badges per product, plus the category / brand / certification / price facets. A row carries the product's primary promotion; `product` lists every promotion. | Pricing teams call search to search one Pick n Pay store's online shelf. | query, store_id, page, page_size, sort, ... |
| category | Every product in one category (department or aisle) at one store, paged, with the same rows, filters and sorts as search; the `categories` facet lists the sub-categories with their codes. | Marketplace operators call category to get every product in one category (department or aisle) at one store, paged, with the same rows,…. | category_id, store_id, page, page_size, sort, ... |
| promotions | Everything on promotion at one store right now — sale prices, Smart Shopper card prices, multi-buys and combos — optionally inside one category or for one search term. | Catalog enrichment teams call promotions to get everything on promotion at one store right now. | store_id, query, category_id, page, page_size, ... |
| promotion | One promotion by the `promotion_id` a product row carries: every product it covers at one store (a combo's whole bundle, a multi-buy's qualifying items) with the promotion's text, deal price, card requirement and dates. | Retail analysts call promotion to get one promotion by the `promotion_id` a product row carries. | promotion_id, store_id, page, page_size |
| product | One product at one store by the `product_id` a search row returns: price, was-price, Smart Shopper price, multi-buy / combo, every promotion with dates, price per kg and allowed weights, stock level, barcode, brand, description, features, allergens, certifications, nutrition table, storage, pack variants (single / case) with their own price and stock, categories, images. | Pricing teams call product to get one product at one store by the `product_id` a search row returns. | product_id, store_id |
| stores | The Pick n Pay stores of the online shop with their store_id (for every other action): code, name, display name and a closed flag where the store's own name says it is closed. Narrow by `query`. | Marketplace operators call stores to get the Pick n Pay stores of the online shop with their store_id (for every other action). | query, include_closed, limit |
Call search from your stack
curl -X POST https://api.reefapi.com/pnp/v1/search \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"query":"milk","store_id":"WC21"}'import requests
r = requests.post(
"https://api.reefapi.com/pnp/v1/search",
headers={"x-api-key": REEF_KEY},
json={
"query": "milk",
"store_id": "WC21"
},
)
print(r.json()["data"])const res = await fetch("https://api.reefapi.com/pnp/v1/search", {
method: "POST",
headers: {
"x-api-key": process.env.REEF_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
"query": "milk",
"store_id": "WC21"
}),
});
const { ok, data, meta, error } = await res.json();Ask your MCP-connected assistant: call reefapi.pnp.search with {"query":"milk","store_id":"WC21"}.Who uses this API and why
- Compare Pick n Pay grocery prices store by store across South Africa.
- Track Smart Shopper prices, multi-buys and combo deals at the stores you watch.
- Monitor price per kg for meat and produce, and was-prices for real discounts.
- Build a South African grocery product database with barcodes, allergens and nutrition tables.
- Check stock levels of a product at a store before sending a shopper.
Questions developers ask before integrating
Do I need a Pick n Pay or Smart Shopper account?
No. Everything is read logged out: the prices, Smart Shopper prices, deals and stock any visitor sees for that store. Personal Smart Shopper offers that need a login are not included.
Is the Smart Shopper price the price I get?
Only with the card. price is what anyone pays; member_price is the Smart Shopper card price. On 2026-10-15, 30 of 288 search rows carried a card price and 19 a multi-buy or combo.
Why does the price depend on the store?
Because Pick n Pay sets prices, deals, stock and even the range per store: 11 of 19 products shared by four stores had different prices on the same day. Pass a store_id from the stores call; without it the site's default store (Pick n Pay Constantia, WC21) is used and the answer says so.
Which stores are covered?
The 438 store codes of pnp.co.za, 386 of them not marked closed. Some stores have no online shop (wholesale and closed stores); a search there returns a clear not-found answer. The store list gives the code and name only; addresses and opening hours are not published there.
Which filters and sorts work?
Sort by relevance or price; filter by brand, category, on promotion, price range in rand and certification (halaal, kosher, free range). Each was checked against the unfiltered count: "milk" 1,271, Clover 125, Milk, Dairy & Eggs 975, on promotion 334, R20-R30 168, kosher 508.
Does the price filter use the Smart Shopper price?
No. min_price and max_price filter on the shelf price: for R20-R30, every returned row's price was inside the range.
How many products per page?
24 by default, up to 48. Past the last page the answer is an empty page with a note. A search for everything stops counting at 10,000 results.
How fresh is the data?
Every call reads the site live; nothing is cached. Promotions carry their own start and end dates.
What is the Pick n Pay API?
Pick n Pay API is a ReefAPI endpoint group for south african grocery prices per store from pick n pay: price in rand, smart shopper price, multi-buy and combo deals, stock and product facts. It returns live JSON through POST requests under /pnp/v1.
Is the Pick n Pay API free to try?
Yes. ReefAPI starts with 1,000 free credits, no card required. Pick n Pay calls use the same shared credit balance as every other ReefAPI engine.
Do I need a Pick n Pay login or account?
No login to Pick n Pay is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.
How fresh is the Pick n Pay data?
The page example is captured from a live search call, and production requests fetch live data through ReefAPI rather than a static sample.
How many credits does the Pick n Pay API use?
Pick n Pay actions currently cost 1-2 credits per successful call. Failed or blocked calls are free. All APIs draw from one credit pool.
Can I call Pick n Pay from an AI assistant or MCP client?
Yes. Connect ReefAPI once through MCP and your assistant can call pnp actions with the same key, credit pool and JSON envelope used by normal REST requests.