# Kolesa.kz API scraper — search Kazakhstan's largest car marketplace (161 697 live car ads measured on 2026-10-02) by keyword, brand, model, city or region, price band in tenge, model year, mileage, engine size, body style, gearbox, fuel, colour, drivetrain, steering side, new/used, and dealer-only, with seven sorts. Every row carries the price in KZT beside the source's own printed string, the brand and model, the city and region, whether the seller is a private owner or a business, and Kolesa's own average market price for that model. Full ad detail returns the complete description, every photo at full size, the whole parameter table (generation, body, engine, mileage, gearbox, drivetrain, steering, colour, customs status), the option list and the seller's public block including the dealer name, address, verification badge and ad count. No account, logged-out public data only, no phone numbers.

> Search Kolesa.kz car classifieds. Every parameter is optional — with none of them you get the whole board, newest first — and each filter below was measured narrowing the same 161 697-row control in the same run. Returns 20 rows a page with the source's own total, and tells you how many of those rows paging can actually reach (the source stops at page 1000).
> ReefAPI engine `kolesa` · 2 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/kolesa/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/kolesa/v1/search — 2 credits
Search Kolesa.kz car classifieds. Every parameter is optional — with none of them you get the whole board, newest first — and each filter below was measured narrowing the same 161 697-row control in the same run. Returns 20 rows a page with the source's own total, and tells you how many of those rows paging can actually reach (the source stops at page 1000).

**Parameters:**
- `query` (string, optional) — Free-text keywords, exactly as typed into the site's own search box (Russian or Latin). Measured bite: the 161 697-row control narrowed to 11 356 for `camry`. Combine freely with `brand` (brand=toyota&query=camry → 10 799).
- `brand` (string, optional) — Brand slug as it appears in Kolesa's own URLs — 'toyota', 'bmw', 'mercedes-benz', 'hyundai', 'lada'. Measured bite: 161 697 → 29 943 for toyota. An unknown slug returns NOT_FOUND because the source answers it with HTTP 404. Every search row returns its own `brand`, so the spelling never has to be guessed twice.
- `model` (string, optional) — Model slug, e.g. 'camry', 'land-cruiser', 'x5'. 🔴 Only works WITH `brand`: the source answers /cars/<model>/ with HTTP 404. Measured bite: toyota 29 943 → toyota+camry 10 454.
- `city` (string, optional) — City slug as Kolesa writes it: almaty (42 852 rows), astana (20 391), shymkent (13 725), karaganda, aktobe, atyrau, aktau, uralsk, pavlodar, taldykorgan and the rest — every search row returns its own `city` slug. Cannot be combined with `region`: the source lets the region win and drops the city, so the pair is rejected here instead of answering the wrong question.
- `region` (enum, optional) — Kazakhstan region (oblast). Measured bite: 161 697 → 44 535 for almaty-region. Mutually exclusive with `city` (see `city`). [one of: abay, akmola, aktobe, almaty-region, atyrau, east-kazakhstan, karaganda, kostanay, kyzylorda, mangystau, north-kazakhstan, pavlodar, turkestan, ulytau, west-kazakhstan, zhambyl, zhetysu]
- `price_min` (integer, optional) — Minimum asking price in **tenge (KZT)** — this site quotes nothing else. Measured bite: the 161 697-row control narrowed to 19 497 for 2 000 000–3 000 000 ₸. A band excludes ads with no price.
- `price_max` (integer, optional) — Maximum asking price in **tenge (KZT)** — this site quotes nothing else. Measured bite: the 161 697-row control narrowed to 19 497 for 2 000 000–3 000 000 ₸. A band excludes ads with no price.
- `year_min` (integer, optional) — Minimum model year. Measured bite: 161 697 → 35 261 for year_min=2020.
- `year_max` (integer, optional) — Maximum model year. Measured bite: 161 697 → 35 261 for year_min=2020.
- `mileage_max` (integer, optional) — Highest odometer reading in km. Measured bite: 161 697 → 19 406 for 50 000 km. The source publishes no lower bound for mileage, so there is no `mileage_min` — it would be a handle that does nothing.
- `engine_volume_min` (number, optional) — Minimum engine displacement in litres. Measured bite: 161 697 → 44 208 for 1.3–1.6 l.
- `engine_volume_max` (number, optional) — Maximum engine displacement in litres. Measured bite: 161 697 → 44 208 for 1.3–1.6 l.
- `body` (enum, optional) — Body style, the source's own 18 values. Measured bite: 161 697 → 30 864 for crossover. An unknown value is rejected here, because the source would accept and ignore it. [one of: convertible, coupe, crossover, fastback, hardtop, hatchback, liftback, limousine, microvan, minibus, minivan, offroad, pickup, roadster, sedan, targa, van, wagon]
- `transmission` (enum, optional) — Gearbox. Measured bite: 161 697 → 53 372 for manual. [one of: manual, automatic, tiptronic, cvt, dual-clutch, any-automatic]
- `fuel` (enum, optional) — Engine type. Measured bite: 161 697 → 1 254 for electric — on this market EVs are 0.8 % of the board, and that is the kind of number this filter is for. [one of: petrol, diesel, lpg-petrol, lpg, hybrid, electric]
- `color` (enum, optional) — Exterior colour, the source's own 21 values. Measured bite: 161 697 → 31 047 for white and 161 708 → 7 635 for blue. An unknown value is rejected here because the source answers an unknown colour id with a convincing empty page (measured). [one of: beige, black, blue, bronze, brown, chameleon, cherry, gold, green, grey, light-blue, lilac, maroon, orange, pink, red, silver, turquoise, violet, white, yellow]
- `steering` (enum, optional) — Which side the steering wheel is on. Measured bite: 161 697 → 9 578 right-hand-drive cars, which on a Japanese-import market is a filter buyers actually use. [one of: left, right]
- `drive` (enum, optional) — Drivetrain. Measured bite: 161 697 → 48 905 for all-wheel drive. [one of: front, all, rear]
- `condition` (enum, optional) — New or used. Measured bite: 161 697 → 4 667 new and 157 031 used, i.e. 97 % of this board is second-hand. [one of: new, used]
- `vehicle_class` (enum, optional) — Coarse vehicle class, one level above `body`. Measured bite: 161 697 → 47 435 for suv-pickup. [one of: car, suv-pickup, minivan-minibus]
- `dealers_only` (boolean, optional, default false) — Only ads from businesses and dealers. Measured bite: 161 697 → 10 055, and cross-checked against the rows themselves — all 20 rows of the filtered page came back with a business `seller_kind`, all 20 of the unfiltered control with a private one. There is no inverse flag: the source's own `who` parameter returns an empty page for every value tried, so it is not offered. Filter `seller_kind == "private"` on the rows instead.
- `customs_cleared` (boolean, optional, default false) — Only cars already customs-cleared in Kazakhstan — the question every buyer of a grey import asks. Measured bite: 161 697 → 157 582, so it rarely narrows much.
- `damaged_only` (boolean, optional, default false) — Only cars the seller marks as damaged or not roadworthy (Аварийная/Не на ходу). Measured bite: 161 697 → 3 232.
- `with_photo` (boolean, optional, default false) — Only ads with at least one photo. Measured bite: 161 697 → 159 960 — on this site 98.9 % of ads already have one.
- `sort` (enum, optional, default "default") — Ordering. All six named orders were measured changing the row order on the same query (price_asc opened at 10 000 ₸, price_desc at 1 000 000 000 ₸). An unknown value is rejected here because the source accepts it and silently keeps its own order. `default` is the source's own order, which measured identical to `newest`. [one of: default, newest, oldest, price_asc, price_desc, year_desc, year_asc]
- `page` (integer, optional, default 1) — 1-based page number, 20 rows a page. 🔴 The source stops at page 1000 — its own page selector says so ('1 из 1000') and pages 1001, 1500 and 4000 all return exactly what page 1000 returns. So one query hands over at most 20 000 of its rows; narrow it with brand, model, city or a price band instead. Every response reports `reachable_rows` for this.

**Returns:** total_results (the source's own count), reachable_rows, page, page_size, page_count, has_more, page_at_ceiling, promoted_tiles_excluded, sort, query, filters (the path and query string actually sent) and listings[] with listing_id, url, title, brand, model, year, condition, body, engine_volume_l, fuel, transmission, steering, mileage_km, color, options[], description_excerpt, price, price_currency (always KZT), price_kind, price_display (the source's own printed string), market_avg_price (Kolesa's own average for this model), city, city_name, region_code (ISO 3166-2, e.g. KZ-ALM), seller_id, seller_kind (private | business), seller_type_id, is_new, is_trade_in, credit_available, has_video, photo_count, status, availability, updated_at, published_until, paid_services[], promoted, labels[], posted_label, image and spec_line[] (the card's own chips, verbatim).

**Example request body:**
```json
{
  "brand": "toyota",
  "city": "almaty",
  "max_results": 20
}
```

### POST https://api.reefapi.com/kolesa/v1/listing — 2 credits
Full detail of one advert by id or URL: the complete seller description, every photo at full size, the price with the source's own printed string, the entire parameter table, the option list, and the seller's public block (kind, dealer name, verification badge, address, ads on file, phone prefix and how many numbers are on file). A removed or non-existent advert returns NOT_FOUND. The seller's phone number itself is never requested and never returned, and a PRIVATE seller's display name is not published on this page at all — `seller.name` is null there and `seller.name_published` says so.

**Parameters:**
- `listing_id` (string, optional) — Advert id — the number at the end of every kolesa.kz/a/show/<id> URL and the `listing_id` of every search row. Give this or `url`.
- `url` (string, optional) — Full advert URL exactly as `search` returns it; the id is taken from its /a/show/<id> tail. Give this or `listing_id`.

**Returns:** listing_id, url, title, brand, model, year, equipment, generation, body, body_text, engine_text, engine_volume_l, fuel, mileage_km, transmission, transmission_text, drive_text, steering, color, color_text, customs_cleared, vin, city, city_name, region_code, description (full text), options[], parameters[] (the source's own label/value rows), images[], image_count, photo_count, price, price_currency, price_kind, price_display, market_avg_price, status, availability, updated_at, published_until, is_new, is_trade_in, credit_available, has_video, paid_services[], promoted and seller{seller_id, kind, seller_type_id, name, name_published, is_verified_dealer, shop_url, experience_text, shop_listing_count, address, labels[], phone_prefix, phone_count, phone_hidden}.

**Example request body:**
```json
{
  "url": "https://kolesa.kz/a/show/231475478"
}
```

## 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=kolesa
- Human docs page: https://reefapi.com/docs/kolesa
- Overview page: https://reefapi.com/kolesa-api
- Every ReefAPI API in one file (for your AI): https://reefapi.com/llms-full.txt
