# Cars.co.za — South African car listings, dealers and dealer stock

> Search Cars.co.za's live South African stock — 76,076 listings on 2026-10-01, of which 65,553 used and 10,523 new, from 1,710 dealerships plus 211 private sellers. Every row carries the listing id, the canonical URL, the asking price in rand, the odometer, the full spec summary, the deal badge Cars.co.za itself puts on the car, all photo URLs, and the seller block (dealership name, suburb, province, coordinates and rating). 🔴 Measured: search rows carry NO phone number — 0 of 120 — because the source only publishes the dealership's tracking number on `detail` and `dealers` (99 of 100 dealerships there have one). `total_available` is the source's own count for your exact query, so you can see what each filter did. 🔴 One call returns at most 120 rows (measured ceiling) — use `offset` to page, which was measured working to the end of the catalogue, with no overlap between pages. All filters are optional; an unmatched filter returns `ok:true` with 0 results and a warning, never an error.
> ReefAPI engine `cars-co-za` · 6 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/cars-co-za/v1/<action>` with a JSON body.
- **Auth:** header `x-api-key: <YOUR_REEFAPI_KEY>` — create one free (1,000 credits, no card): https://reefapi.com/signup
- **Response (every call):** `{ ok: boolean, data: ..., meta: { record_count, credits, ... }, error: { code, message } }` — branch on `ok`. Failed calls are free except verified SHEIN NOT_FOUND on product/detail and price (4 credits).
- **One key + one shared credit pool** across every ReefAPI API. Per-call credits are listed on each endpoint below.
- **Use it from an AI agent (MCP):** connect `https://api.reefapi.com/mcp` (remote streamable-http). Send the key as `Authorization: Bearer <key>`, or put it in the URL (`?key=<key>`) when the client has no header field, as ChatGPT does.

## Endpoints

### POST https://api.reefapi.com/cars-co-za/v1/search — 2 credits
Search Cars.co.za's live South African stock — 76,076 listings on 2026-10-01, of which 65,553 used and 10,523 new, from 1,710 dealerships plus 211 private sellers. Every row carries the listing id, the canonical URL, the asking price in rand, the odometer, the full spec summary, the deal badge Cars.co.za itself puts on the car, all photo URLs, and the seller block (dealership name, suburb, province, coordinates and rating). 🔴 Measured: search rows carry NO phone number — 0 of 120 — because the source only publishes the dealership's tracking number on `detail` and `dealers` (99 of 100 dealerships there have one). `total_available` is the source's own count for your exact query, so you can see what each filter did. 🔴 One call returns at most 120 rows (measured ceiling) — use `offset` to page, which was measured working to the end of the catalogue, with no overlap between pages. All filters are optional; an unmatched filter returns `ok:true` with 0 results and a warning, never an error.

**Parameters:**
- `query` (string, optional) — Free-text keyword, exactly as the site's own search box treats it — matched against make, model, variant and the dealer's description. Measured: `Hilux` → 2,642 of 76,076 listings.
- `make` (string, optional) — Manufacturer, spelled as Cars.co.za spells it (`Toyota`, `Volkswagen`, `Mercedes-Benz`, `Haval`, `Chery`, `GWM`). 100 makes are listed; call the `filters` action with `facet=make` for the full list with live counts. Measured: `Toyota` → 11,519. An unknown make is not an error: you get `ok:true` with 0 results and a warning.
- `model` (string, optional) — Model name within the make (`Starlet`, `Hilux`, `Polo Vivo`). Measured: `make=Toyota` + `model=Starlet` → 1,056. `filters` with `facet=make_model_variant` returns the whole make → model → variant tree with a count on every node.
- `variant` (string, optional) — Derivative / trim as the source prints it. Measured: `variant=1.4 XR` → 50; with make+model → 48.
- `condition` (enum, optional) — New or used. Measured in one run: Used 65,553 + New 10,523 = 76,076, i.e. exactly the unfiltered total, so the two buckets are complete and disjoint. Omit to get both. [one of: Used, New]
- `seller_kind` (enum, optional) — Dealership stock or a private individual's car. Measured: dealer 75,865, private 211 (= 76,076). Private rows publish no seller name beyond `Private Seller` and no contact route. [one of: dealer, private]
- `price_min` (integer, optional) — Lowest asking price in rand (ZAR). Sent as the source's own range form. Measured: 100,000–200,000 → 13,566; 0–50,000 → 163.
- `price_max` (integer, optional) — Highest asking price in rand (ZAR).
- `mileage_min` (integer, optional) — Lowest odometer reading in kilometres. Measured: 0–50,000 → 40,453.
- `mileage_max` (integer, optional) — Highest odometer reading in kilometres. 1,135 listings publish no odometer at all (the source's own count) and are excluded by any mileage bound.
- `year_min` (integer, optional) — Earliest model year. Measured: 2023–2026 → 43,911.
- `year_max` (integer, optional) — Latest model year. The source lists model years up to 2027.
- `instalment_min` (integer, optional) — Lowest monthly finance instalment in rand, using the site's own rate card (12.75 % p.a., 72 months, 10 % deposit). Only 144 of 200 sampled rows carry an instalment at all — private listings never do.
- `instalment_max` (integer, optional) — Highest monthly finance instalment in rand. Measured: 0–3,000 → 3,148.
- `engine_litres_min` (number, optional) — Smallest engine capacity in litres. Measured: 1.0–1.6 → 38,127.
- `engine_litres_max` (number, optional) — Largest engine capacity in litres. Electric cars publish no capacity (0 of 20 sampled) and are excluded by any capacity bound.
- `body_type` (enum, optional) — Body style, exactly as Cars.co.za names it — note `Double Cab Bakkie` for a double-cab pickup, which is the second-largest body class in South Africa. Measured: SUV → 33,823; Bus → 1,474; Panel Van → 755. The four motorcycle body styles only return rows when `bikes=true`. [one of: SUV, Hatchback, Double Cab Bakkie, Sedan, Single Cab Bakkie, Multi Purpose Vehicle, Bus, Coupe, Extended Cab Bakkie, Panel Van, Cabriolet, Dropside, Station Wagon, LDV with Flat Body, Road, Offroad, Dual Purpose, 4 Wheel]
- `province` (enum, optional) — South African province. Measured: Gauteng → 42,243 (55 % of the market), Northern Cape → 301. [one of: Gauteng, Western Cape, Kwazulu Natal, Mpumalanga, North West Province, Eastern Cape, Free State, Limpopo, Northern Cape]
- `city` (string, optional) — Town / suburb the selling dealership sits in, as the source prints it (`Sandton`, `Pietermaritzburg`, `Klerksdorp`). Free-form, not a closed list: call `filters` with `facet=province` for the regional split, and read `seller.locality` on any result for the exact spelling. Measured: `Sandton` → 2,881.
- `transmission` (enum, optional) — Gearbox. Measured: Automatic 48,673, Manual 27,390. [one of: Automatic, Manual]
- `fuel_type` (enum, optional) — Fuel. Measured: Petrol 48,574, Diesel 24,743, Hybrid 1,419, PHEV 659, Electric 596. [one of: Petrol, Diesel, Hybrid, PHEV, Electric]
- `colour` (enum, optional) — Exterior colour as the source groups it. Measured: White 36,379 — 48 % of the South African market. [one of: White, Silver, Grey, Blue, Black, Red, Green, Brown, Gold, Orange, Beige, Yellow, Purple, Pink, Unknown]
- `seats` (enum, optional) — Seat count. The source publishes `8+` as one bucket and uses `0` for 'not recorded' (163 listings). Measured: 5 → 58,766; 7 → 7,152. [one of: 1, 2, 3, 4, 5, 7, 8+, 0]
- `drivetrain` (enum, optional) — Driven wheels, as the source's `vehicle_axle_config`. Measured: 4X2 58,939, 4X4 16,921. [one of: 4X2, 4X4]
- `vehicle_kind` (enum, optional) — Passenger car or commercial vehicle. Measured: passenger 74,900, commercial 16,886 — the two overlap, because a double-cab bakkie counts as both. [one of: passenger, commercial]
- `engine_type` (enum, optional) — Induction type, where the source records it. Measured: Turbocharged 46,242, Supercharged 172, Naturally Aspirated 128 — 29,534 listings record none. [one of: Turbocharged, Supercharged, Naturally Aspirated]
- `deal_tag` (enum, optional) — Cars.co.za's own badge on the listing. `great deal` / `good deal` / `fair deal` are its price verdict against its own guide price; `price drop` means the advertised price was cut. Measured: great deal 7,433, price drop 4,845. 33,631 listings carry no badge. [one of: great deal, good deal, fair deal, price drop, fast seller, performance, luxury, classic]
- `dealer_rating_min` (number, optional) — Only cars at dealerships rated at least this (out of 5) by the site's own review feed. Measured: 4 and up → 57,941.
- `featured_dealers_only` (boolean, optional, default false) — Only stock at dealerships Cars.co.za flags as featured. Measured: 10,790 listings at 198 of the 1,710 dealerships.
- `bikes` (boolean, optional, default false) — Include motorcycles, which the car search leaves out. Measured: the unfiltered total goes from 76,076 to 76,125, i.e. Cars.co.za is listing 49 bikes — it is a car marketplace and the bike surface is thin. Pair with `body_type=Road` / `Offroad` / `Dual Purpose` / `4 Wheel`.
- `sort` (enum, optional, default "relevance") — Result order. An unknown sort key makes the source answer with no data at all (measured), so this is a closed list and a bad value is rejected here rather than forwarded. [one of: relevance, price_asc, price_desc, mileage_asc, mileage_desc, year_desc, year_asc, newest_stock, instalment_asc]
- `max_results` (integer, optional, default 20) — Rows to return, 1-120. 120 is the source's measured hard ceiling: asking for 200 or 500 still returns 120. Use `offset` to page past it — offset paging was measured to the end of the catalogue.
- `offset` (integer, optional, default 0) — Rows to skip. Measured working at 0, 1,000, 20,000 and 76,060; past the end (100,000) the source returns 0 rows and no error. Pages do not overlap: 4 x 20 rows at offsets 0/20/40/60 gave 80 distinct ids.

**Returns:** {results[], total_available, returned, offset, page_ceiling, sort} — each result: listing_id, url, title, make, model, variant, year, condition, price_zar, previous_price_zar, monthly_instalment_zar, currency, mileage_km, mileage_text, body_type, transmission, fuel_type, engine_litres, engine_type, drivetrain, seats, colour, vehicle_kind, deal_tags[], condition_grade, description, features[], images[], image_count, province, city, seller{}, listing_code.

**Example request body:**
```json
{
  "max_results": 20
}
```

### POST https://api.reefapi.com/cars-co-za/v1/detail — 1 credit
One listing in full, from the source's own listing endpoint: everything a `search` row carries plus the complete manufacturer spec table grouped as Cars.co.za groups it (summary, engine, performance, dimensions, …), the site's own `highlights` verdicts with their numbers, the date the car was listed, the dealership's own stock reference, and the sold flag. Measured on a dealer car, a private-seller car and a motorcycle: all three answer, and motorcycles come back with an empty spec table because the source publishes none for them. A dead id returns NOT_FOUND.

**Parameters:**
- `listing_id` (string, required) — Cars.co.za listing id — the last path segment of a /for-sale/ URL and the `listing_id` of any `search` row. A dead id returns NOT_FOUND.

**Returns:** One listing object: every search field plus specs[] (grouped label/value), highlights[], listed_at, dealer_reference, is_sold, seller{} with phone and WhatsApp where published.

**Example request body:**
```json
{
  "listing_id": "11264546"
}
```

### POST https://api.reefapi.com/cars-co-za/v1/dealers — 2 credits
The Cars.co.za dealer directory — 1,710 active dealerships on 2026-10-01 — with street address, suburb, province, coordinates, dealer group, rating and rating count, how many cars they currently list, their three most-listed makes, their Cars.co.za profile URL and their published phone number. Filters measured in the same run: `query`/`name` → 32 for BMW, `province` → 858 in Gauteng, `city` → 53 in Sandton, `featured_only` → 198. 🔴 Four parameters the source accepts and then ignores (`popular_makes`, `top_make`, a province under the vehicle spelling, and a rating range) are deliberately NOT exposed.

**Parameters:**
- `query` (string, optional) — Dealership name or free text. Measured: `BMW` → 32 of 1,710 dealerships.
- `province` (enum, optional) — Province the dealership sits in. Measured: Gauteng → 858 dealerships. [one of: Gauteng, Western Cape, Kwazulu Natal, Mpumalanga, North West Province, Eastern Cape, Free State, Limpopo, Northern Cape]
- `city` (string, optional) — Town / suburb. Measured: `Sandton` → 53.
- `featured_only` (boolean, optional, default false) — Only dealerships Cars.co.za flags as featured. Measured: 198 of 1,710.
- `sort` (enum, optional, default "name") — Dealer directory order. [one of: name, name_desc]
- `max_results` (integer, optional, default 20) — Dealerships to return, 1-200. The dealer surface honours 200 (measured), unlike the vehicle surface's 120.
- `offset` (integer, optional, default 0) — Rows to skip. Measured working at 0, 1,000, 20,000 and 76,060; past the end (100,000) the source returns 0 rows and no error. Pages do not overlap: 4 x 20 rows at offsets 0/20/40/60 gave 80 distinct ids.

**Returns:** {results[], total_available, returned, offset} — each result: dealer_id, name, group, address, city, province, latitude, longitude, rating, rating_count, listing_count, top_makes[], phone, whatsapp_enabled, nada_member, is_featured, status, url, showcase_image, map_embed_url.

**Example request body:**
```json
{
  "province": "Gauteng",
  "max_results": 20
}
```

### POST https://api.reefapi.com/cars-co-za/v1/dealer — 2 credits
One dealership's profile, plus the Google reviews Cars.co.za publishes for it (reviewer name, star rating, relative date and the review text) when `include_reviews` is left on. A dealer id that does not exist returns NOT_FOUND rather than an empty success.

**Parameters:**
- `dealer_id` (string, required) — Dealership id as `dealers` and `search` (`seller.dealer_id`) return it.
- `include_reviews` (boolean, optional, default true) — Also fetch the dealership's published Google reviews (one extra upstream request). Set false for the profile alone.

**Returns:** {dealer{…as in `dealers`…}, reviews[], review_count, reviews_available} — reviews: author, rating, text, posted_text.

**Example request body:**
```json
{
  "dealer_id": "8365"
}
```

### POST https://api.reefapi.com/cars-co-za/v1/dealer_stock — 2 credits
Every vehicle one dealership currently lists, in the same row shape as `search`, dearest first by default. Take `dealer_id` from `dealers` or from `seller.dealer_id` on any search row. Measured: dealer 8365 → 10 cars, dealer 7914 → 17.

**Parameters:**
- `dealer_id` (string, required) — Dealership id as `dealers` and `search` (`seller.dealer_id`) return it.
- `sort` (enum, optional, default "relevance") — Result order. An unknown sort key makes the source answer with no data at all (measured), so this is a closed list and a bad value is rejected here rather than forwarded. [one of: relevance, price_asc, price_desc, mileage_asc, mileage_desc, year_desc, year_asc, newest_stock, instalment_asc]
- `max_results` (integer, optional, default 20) — Rows to return, 1-120. 120 is the source's measured hard ceiling: asking for 200 or 500 still returns 120. Use `offset` to page past it — offset paging was measured to the end of the catalogue.
- `offset` (integer, optional, default 0) — Rows to skip. Measured working at 0, 1,000, 20,000 and 76,060; past the end (100,000) the source returns 0 rows and no error. Pages do not overlap: 4 x 20 rows at offsets 0/20/40/60 gave 80 distinct ids.
- `bikes` (boolean, optional, default false) — Include motorcycles, which the car search leaves out. Measured: the unfiltered total goes from 76,076 to 76,125, i.e. Cars.co.za is listing 49 bikes — it is a car marketplace and the bike surface is thin. Pair with `body_type=Road` / `Offroad` / `Dual Purpose` / `4 Wheel`.

**Returns:** {results[] as in `search`, total_available, returned, offset, dealer_id}

**Example request body:**
```json
{
  "dealer_id": "8365"
}
```

### POST https://api.reefapi.com/cars-co-za/v1/filters — 1 credit
The live filter vocabulary straight from the source, with the number of listings behind every value — so you can size a query before you run it, and you never have to guess a spelling. `facet=make_model_variant` returns the whole tree (100 makes, every model, every variant, counts on all three levels, ~167 KB). `facet=price` / `mileage` / `year` / `monthly_price` return the source's own two-sided ladder (how many cars sit above each rung and below each rung). Any `search` filter can be narrowed by the same values.

**Parameters:**
- `facet` (enum, required) — Which filter vocabulary to return. Every value comes back with the number of live listings behind it, so you can size a query before you run it. [one of: make, make_model_variant, body_type, province, transmission, fuel_type, colour, seats, commercial_type, vehicle_axle_config, vehicle_tags, engine_type, new_or_used, seller_type, seller_types, engine_capacity, price, mileage, year, monthly_price]

**Returns:** {facet, values{} | tree{}, value_count} — `values` maps each published value to its live listing count; the make/model/variant facet returns a nested tree instead; the four numeric facets return `min`/`max` ladders.

**Example request body:**
```json
{
  "facet": "body_type"
}
```

## At scale
- **Volume:** 5M+ requests a day, measured at 60 requests a second across the fleet with no
  central bottleneck. Per-key limits are raised for high-volume accounts; volume pricing on request.
- **Missing a source:** tell us a site we do not cover and it becomes an engine. A customer asked
  for bestprice.gr on 21 Sep 2026 and it was in the catalog on 22 Sep.
- **Support:** 2 minute median time from a question in the live chat to the first answer. Setup
  help included, no support tier to buy.
- **One key, one credit pool** across every API. No per-site plans, no separate subscriptions.

## More
- Try it live, no code: https://reefapi.com/playground?engine=cars-co-za
- Human docs page: https://reefapi.com/docs/cars-co-za
- Overview page: https://reefapi.com/cars-co-za-api
- Every ReefAPI API in one file (for your AI): https://reefapi.com/llms-full.txt
