# PakWheels API scraper — Pakistan's largest vehicle marketplace (pakwheels.com). Search the live Pakistani used-car, used-motorcycle and auto-parts markets with the site's own filters (make, model, city, province, body type, fuel, transmission, colour, assembly, origin, dealer vs individual, price in PKR, model year, odometer) and pull the full ad: exact rupee price alongside the site's own 'lacs/crore' string, odometer, engine, colour, registration city, every photo, the seller's comments, the feature list and the seller's public profile. No account, no browser.

> Search the live Pakistani used-car market on pakwheels.com. Every row is one ad: make, model, variant, model year, asking price in exact PKR rupees plus the site's own printed string ('PKR 2.07 crore'), odometer in km, fuel, engine size, transmission, city, photo, photo count, whether the seller paid to feature it, whether PakWheels manages the sale, and when it was last bumped. total_results is the site's own count, and page_size, pages and page_window come from the response rather than from a constant. Every filter offered here was measured to move that total in the same run; filters the source accepts and ignores are deliberately absent. Call with no parameters to page the whole market.
> ReefAPI engine `pakwheels` · 7 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/pakwheels/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/pakwheels/v1/car_search — 3 credits
Search the live Pakistani used-car market on pakwheels.com. Every row is one ad: make, model, variant, model year, asking price in exact PKR rupees plus the site's own printed string ('PKR 2.07 crore'), odometer in km, fuel, engine size, transmission, city, photo, photo count, whether the seller paid to feature it, whether PakWheels manages the sale, and when it was last bumped. total_results is the site's own count, and page_size, pages and page_window come from the response rather than from a constant. Every filter offered here was measured to move that total in the same run; filters the source accepts and ignores are deliberately absent. Call with no parameters to page the whole market.

**Parameters:**
- `query` (string, optional) — Free-text keyword, passed to the site's own search box. Measured to bite: 83,786 cars → 10,716 for 'corolla'; 58,934 parts → 661 for 'clutch'.
- `make` (string, optional) — Make, as PakWheels spells it in its own urls (lower-case, hyphenated: 'toyota', 'suzuki', 'honda', 'mercedes-benz'). Measured bite: 83,786 → 22,876 for toyota. Hundreds of makes are listed, so this is an open text token, not a closed enum; an unknown make makes the source answer 404 and you get NOT_FOUND, never a full catalogue.
- `model` (string, optional) — Model slug, used together with make. Measured: toyota 22,876 → toyota+corolla 9,993.
- `city` (string, optional) — City or region slug ('lahore', 'karachi', 'islamabad', 'rawalpindi'…). Measured bite: 83,786 → 11,441 for lahore.
- `province` (enum, optional) — Province. Measured bite: 83,786 → 46,872 for punjab. [one of: punjab, sindh, kpk, balochistan, azad-kashmir, islamabad-capital-territory, gilgit-baltistan, federally-administered-tribal-areas]
- `body_type` (array, optional) — Body type, one or more. Measured bite: 83,786 → 29,232 for sedan. [one of: sedan, hatchback, suv, crossover, van, mini-van, pick-up, double-cabin, single-cabin, coupe, convertible, compact-sedan, compact-suv, mpv, mini-vehicles, station-wagon, truck, high-roof, micro-van, subcompact-hatchback]
- `fuel` (array, optional) — Fuel / drivetrain energy. Measured bite: 83,786 → 72,528 for petrol. [one of: petrol, diesel, hybrid, electric, phev, cng, lpg]
- `transmission` (enum, optional) — Measured bite: 83,786 → 48,222 for automatic. [one of: automatic, manual]
- `color` (array, optional) — Exterior colour. Measured bite: 83,786 → 33,862 for white. [one of: white, black, silver, grey, blue, red, green, brown, maroon, gold, beige, gun-metallic, pearl-white, bronze, burgundy, indigo, navy, orange, pink, purple, turquoise, yellow, unlisted]
- `assembly` (enum, optional) — Locally assembled or imported. Measured: local 60,914, imported 22,872. [one of: local, imported]
- `origin` (enum, optional) — Country-of-origin group as the site groups it. Measured bite: 83,786 → 14,832 for japanese. [one of: japanese, german, korean, chinese, american, european, british, indian, pakistani]
- `seller_kind` (enum, optional) — Who is selling. The two values are the site's own facet labels; measured 73,130 individual ads and 10,656 dealer ads. [one of: individual, dealer]
- `price_min` (integer, optional) — Lowest asking price in PKR (whole rupees — PakWheels prices are written as 'lacs' and 'crore' on the page but this API takes and returns the exact rupee figure). Measured bite: 1,000,000-2,000,000 → 17,473.
- `price_max` (integer, optional) — Highest asking price in PKR. Sent together with price_min — the source wants both halves of the range, so a one-sided request is completed with 0 / 100,000,000.
- `year_min` (integer, optional) — Earliest model year. Measured bite: 2018-2022 → 23,032.
- `year_max` (integer, optional) — Latest model year.
- `mileage_min` (integer, optional) — Lowest odometer reading in km. Measured bite: 0-50,000 → 22,654.
- `mileage_max` (integer, optional) — Highest odometer reading in km.
- `inspected_only` (boolean, optional, default false) — Only ads PakWheels has inspected itself (the site's 'PakWheels Inspected' facet). Measured bite: 83,786 → 2,688.
- `featured_only` (boolean, optional, default false) — Only ads whose seller paid to feature them. Measured bite: 83,786 → 12,873. Note that featured ads are ordinary results, not a separate block: every row carries a `featured` flag.
- `with_pictures` (boolean, optional, default false) — Only ads that have at least one photo. Measured bite: 83,786 → 83,260, so almost every ad has pictures.
- `with_video` (boolean, optional, default false) — Only ads with a video. Measured bite: 83,786 → 186 — this is a very small slice of the market, which is exactly why it is worth saying.
- `sort` (enum, optional, default "bumped_at-desc") — Result order. An explicit order is ALWAYS sent, because with no order the source injects extra promoted rows on top of the page (measured 29 rows for a window it declared as 1-25) and those rows repeat between pages. An unrecognised value is silently dropped by the source, so it is rejected here instead. [one of: bumped_at-desc, bumped_at-asc, price-asc, price-desc, model_year-desc, model_year-asc, mileage-asc, mileage-desc]
- `page` (integer, optional, default 1) — 1-based page. Pages hold 25 rows on the car and bike surfaces and 24 on the parts surface (measured, not assumed). The source serves every page its own total implies — the unfiltered car market's last page is 3352 and it carries the final 10 of 83,785 ads — and a page beyond the last REDIRECTS to the last page rather than replaying page 1. The response reports page_window (what the site says it served), pages and reachable_results so you never have to guess.

**Returns:** {total_results, page, page_size, pages, reachable_results, page_window, has_more, count, duplicates_dropped, filters_applied, search_url, cars[]} — each car has ad_id, url, title, price_pkr, price_currency, price_display, price_before_discount_pkr, price_before_discount_display, price_on_request, city, brand, model, variant, year, mileage_km, fuel, engine_cc, engine_type, transmission, image_url, images_count, featured, managed_by_pakwheels, availability and updated_text.

**Example request body:**
```json
{
  "make": "toyota",
  "city": "lahore",
  "max_results": 20
}
```

### POST https://api.reefapi.com/pakwheels/v1/car_listing — 3 credits
The FULL ad behind a car ad_id: everything the detail page publishes. Adds what a search row cannot carry — the seller's own free-text comments, the complete feature list, every photo at slide size, exterior colour, body type, assembly, the city the car is registered in, the ad reference number, the last-updated date, the site's own label/value spec table verbatim, and the seller block (display name, member-since date, profile image). The one field this surface does NOT publish is the phone number: the page prints a mask and the real number sits behind a click, so no fake field is returned. A dead id answers NOT_FOUND, never an empty success.

**Parameters:**
- `ad_id` (string, required) — The numeric ad id exactly as a search row returns it in ad_id. The slug is not needed: /used-cars/<id> redirects to the canonical url (measured). A removed or non-existent id answers a real HTTP 404 upstream and becomes NOT_FOUND.

**Returns:** {car:{ad_id, url, title, price_pkr, price_currency, price_display, price_on_request, availability, condition, brand, model, year, mileage_km, fuel, transmission, color, body_type, engine_cc, engine_type, assembly, registered_in, last_updated, ad_reference, description, image_url, images[], images_count, specs{}, features[], seller{}, similar_ads_on_page}}

**Example request body:**
```json
{
  "ad_id": "12075989"
}
```

### POST https://api.reefapi.com/pakwheels/v1/bike_search — 3 credits
Search the live Pakistani used-motorcycle and scooter market. Same row shape as the car search where the source publishes the same facts: make, model, year, exact PKR price plus the printed string, odometer, engine type, city, photo, featured flag and bump time. 🔴 This surface is NOT the car surface with a different path — it was measured separately: bike result rows carry no embedded product schema (0 blocks for 25 rows, against 25+ on the car surface), so the row data is read from the row markup and the engine-size, body type, colour and assembly filters the car surface offers are not offered here because they are not part of this surface's own facet set. Measured market size 21,573 bikes.

**Parameters:**
- `query` (string, optional) — Free-text keyword, passed to the site's own search box. Measured to bite: 83,786 cars → 10,716 for 'corolla'; 58,934 parts → 661 for 'clutch'.
- `make` (string, optional) — Make, as PakWheels spells it in its own urls (lower-case, hyphenated: 'toyota', 'suzuki', 'honda', 'mercedes-benz'). Measured bite: 83,786 → 22,876 for toyota. Hundreds of makes are listed, so this is an open text token, not a closed enum; an unknown make makes the source answer 404 and you get NOT_FOUND, never a full catalogue.
- `model` (string, optional) — Model slug, used together with make. Measured: toyota 22,876 → toyota+corolla 9,993.
- `city` (string, optional) — City or region slug ('lahore', 'karachi', 'islamabad', 'rawalpindi'…). Measured bite: 83,786 → 11,441 for lahore.
- `province` (enum, optional) — Province. Measured bite: 83,786 → 46,872 for punjab. [one of: punjab, sindh, kpk, balochistan, azad-kashmir, islamabad-capital-territory, gilgit-baltistan, federally-administered-tribal-areas]
- `fuel` (array, optional) — Fuel / drivetrain energy. Measured bite: 83,786 → 72,528 for petrol. [one of: petrol, diesel, hybrid, electric, phev, cng, lpg]
- `seller_kind` (enum, optional) — Who is selling. The two values are the site's own facet labels; measured 73,130 individual ads and 10,656 dealer ads. [one of: individual, dealer]
- `price_min` (integer, optional) — Lowest asking price in PKR (whole rupees — PakWheels prices are written as 'lacs' and 'crore' on the page but this API takes and returns the exact rupee figure). Measured bite: 1,000,000-2,000,000 → 17,473.
- `price_max` (integer, optional) — Highest asking price in PKR. Sent together with price_min — the source wants both halves of the range, so a one-sided request is completed with 0 / 100,000,000.
- `year_min` (integer, optional) — Earliest model year. Measured bite: 2018-2022 → 23,032.
- `year_max` (integer, optional) — Latest model year.
- `mileage_min` (integer, optional) — Lowest odometer reading in km. Measured bite: 0-50,000 → 22,654.
- `mileage_max` (integer, optional) — Highest odometer reading in km.
- `featured_only` (boolean, optional, default false) — Only ads whose seller paid to feature them. Measured bite: 83,786 → 12,873. Note that featured ads are ordinary results, not a separate block: every row carries a `featured` flag.
- `with_pictures` (boolean, optional, default false) — Only ads that have at least one photo. Measured bite: 83,786 → 83,260, so almost every ad has pictures.
- `with_video` (boolean, optional, default false) — Only ads with a video. Measured bite: 83,786 → 186 — this is a very small slice of the market, which is exactly why it is worth saying.
- `sort` (enum, optional, default "bumped_at-desc") — Result order. An explicit order is ALWAYS sent, because with no order the source injects extra promoted rows on top of the page (measured 29 rows for a window it declared as 1-25) and those rows repeat between pages. An unrecognised value is silently dropped by the source, so it is rejected here instead. [one of: bumped_at-desc, bumped_at-asc, price-asc, price-desc, model_year-desc, model_year-asc, mileage-asc, mileage-desc]
- `page` (integer, optional, default 1) — 1-based page. Pages hold 25 rows on the car and bike surfaces and 24 on the parts surface (measured, not assumed). The source serves every page its own total implies — the unfiltered car market's last page is 3352 and it carries the final 10 of 83,785 ads — and a page beyond the last REDIRECTS to the last page rather than replaying page 1. The response reports page_window (what the site says it served), pages and reachable_results so you never have to guess.

**Returns:** Same envelope as car_search with a bikes[] list; each bike has ad_id, url, title, price_pkr, price_currency, price_display, price_on_request, city, brand, model, year, mileage_km, engine_type ('4 Stroke' / '2 Stroke' / 'Electric' - 36/36 filled), image_url, images_count, featured, managed_by_pakwheels and updated_text. Engine size, gearbox and availability are NOT returned: this surface does not publish them (measured 0/36).

**Example request body:**
```json
{
  "query": "corolla"
}
```

### POST https://api.reefapi.com/pakwheels/v1/bike_listing — 2 credits
The FULL ad behind a bike ad_id: the seller's comments, the bike-feature list (disc brake, LED light…), every photo, colour, registered-in city, assembly, body type, engine type, the ad reference, the last-updated date, the site's own spec table verbatim and the seller block. A dead id answers NOT_FOUND.

**Parameters:**
- `ad_id` (string, required) — The numeric ad id exactly as a search row returns it in ad_id. The slug is not needed: /used-cars/<id> redirects to the canonical url (measured). A removed or non-existent id answers a real HTTP 404 upstream and becomes NOT_FOUND.

**Returns:** {bike:{…same shape as car_listing.car, minus the car-only keys the bike page does not publish}}

**Example request body:**
```json
{
  "ad_id": "12075989"
}
```

### POST https://api.reefapi.com/pakwheels/v1/part_search — 3 credits
Search PakWheels' auto-parts and accessories marketplace: 58,934 live listings measured on 2026-10-02. Every row is one part: title, exact PKR price, the site's own two-level category path (for example 'Engine & Mechanical' → 'Car Clutch Plate'), photo, photo count and whether it is available for instant Buy Now checkout. 🔴 Only the keyword filter is offered on this surface, and that is a measurement, not an omission: the city filter that bites on cars and bikes is accepted with HTTP 200 and silently ignored here (58,934 → 58,934, byte-identical), so it is not exposed. Keyword does bite: 58,934 → 661 for 'clutch'. Pages hold 24 rows on this surface, not 25.

**Parameters:**
- `query` (string, optional) — Free-text keyword, passed to the site's own search box. Measured to bite: 83,786 cars → 10,716 for 'corolla'; 58,934 parts → 661 for 'clutch'.
- `sort` (enum, optional, default "bumped_at-desc") — Result order. An explicit order is ALWAYS sent, because with no order the source injects extra promoted rows on top of the page (measured 29 rows for a window it declared as 1-25) and those rows repeat between pages. An unrecognised value is silently dropped by the source, so it is rejected here instead. [one of: bumped_at-desc, bumped_at-asc, price-asc, price-desc, model_year-desc, model_year-asc, mileage-asc, mileage-desc]
- `page` (integer, optional, default 1) — 1-based page. Pages hold 25 rows on the car and bike surfaces and 24 on the parts surface (measured, not assumed). The source serves every page its own total implies — the unfiltered car market's last page is 3352 and it carries the final 10 of 83,785 ads — and a page beyond the last REDIRECTS to the last page rather than replaying page 1. The response reports page_window (what the site says it served), pages and reachable_results so you never have to guess.

**Returns:** {total_results, page, page_size, pages, reachable_results, page_window, has_more, count, duplicates_dropped, filters_applied, search_url, parts[]} — each part has ad_id, url, title, price_pkr, price_currency, price_display, price_before_discount_pkr, price_before_discount_display, price_on_request, category, sub_category, image_url, images_count, buy_now, featured.

**Example request body:**
```json
{
  "query": "corolla"
}
```

### POST https://api.reefapi.com/pakwheels/v1/part_listing — 3 credits
The full parts/accessory ad behind an ad_id: the seller's own description, the stock keeping id the site assigns, the brand, every photo and the site's own spec table. A dead id answers NOT_FOUND.

**Parameters:**
- `ad_id` (string, required) — The numeric ad id exactly as a search row returns it in ad_id. The slug is not needed: /used-cars/<id> redirects to the canonical url (measured). A removed or non-existent id answers a real HTTP 404 upstream and becomes NOT_FOUND.

**Returns:** {part:{ad_id, url, title, price_pkr, price_currency, price_display, availability, brand, sku, description, image_url, images[], images_count, specs{}, features[], seller{}}}

**Example request body:**
```json
{
  "ad_id": "12075989"
}
```

### POST https://api.reefapi.com/pakwheels/v1/filter_options — 1 credit
The filter vocabulary this API accepts, group by group, with the label PakWheels itself prints next to each value and the measured ad count behind the ones that were counted. Read this before building a filter UI. It is served from the vocabulary the engine validates against, so it costs no upstream request and can never drift away from what the API will accept. Make, model and city are deliberately NOT enumerated here: PakWheels lists hundreds of each and they are open text tokens in its own urls.

**Parameters:** none

**Returns:** {sorts[], body_types[], fuels[], transmissions[], colors[], assembly[], origins[], seller_kinds[], provinces[], open_text_filters[], notes{}}

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