PakWheels API & Scraper
PakWheels is where vehicles are bought and sold in Pakistan, and this API turns its three marketplaces into JSON.
🤖 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.
Searching cars returns 25 ads a page out of 83,787, each with the make, model and trim, the model year, the asking price as an exact rupee figure, the odometer in kilometres, fuel, engine size, gearbox, the city, the photo, the photo count, whether the seller paid to feature the ad and when it was last bumped. The bike surface works the same way over 21,573 motorcycles and scooters, and the parts surface over 58,934 accessory and spare-part listings with the site's own two-level category path. Give an ad id back to the detail endpoint and you get the whole page: the seller's own comments, the complete feature list, every photo, the exterior colour, the city the vehicle is registered in, assembly, the ad reference and the seller's public profile. Prices are the honest part. PakWheels writes them in local scale on the page, so the same car reads 'PKR 2.07 crore' in the list and 'PKR 207.0 lacs' on its own ad, and we return the exact integer 20700000 next to the site's own string so your numbers never pick up a hundred-fold error. On 375 rows across the three marketplaces the integer and the printed string agreed 375 times and disagreed none. Twenty filters are offered and all twenty were measured to move the result count, which is why the city filter is missing from the parts endpoint: the site accepts it there and quietly ignores it.
What a PakWheels search row carries
Measured on 2026-10-02 over 250 car rows from ten different queries, 75 bike rows and 50 parts rows. The three columns that are not 100 % are real gaps in the source, not parser misses.
| Field | What it is | Filled |
|---|---|---|
| ad_id | Numeric PakWheels ad id; feed it straight back to the detail endpoint | 100 % |
| price_pkr | Asking price as an exact whole-rupee integer | 242/250 cars, 75/75 bikes, 50/50 parts |
| price_display | The price string PakWheels itself prints ('PKR 2.07 crore') | 100 % |
| price_before_discount_pkr | The struck-through old price, when the ad shows one | only on discounted ads |
| brand / model / variant | Make, model and trim as the site tags them | 100 % / 100 % / 237/250 |
| year / mileage_km | Model year and odometer in kilometres | 100 % / 100 % |
| fuel / engine_cc / transmission | Petrol, diesel, hybrid, electric, PHEV; engine size; gearbox (cars) | 100 % / 247/250 / 100 % |
| engine_type | Bikes only: '4 Stroke', '2 Stroke' or 'Electric' | 75/75 bikes |
| city | The city the ad is posted in | 100 % cars and bikes |
| category / sub_category | Parts only: the site's own two-level category path | 50/50 parts |
| featured | Whether the seller paid to feature this ad | always present |
| image_url / images_count | Lead photo and how many photos the ad has | 100 % |
| updated_text | When the ad was last bumped, as the site words it | 100 % cars and bikes |
Eight of 250 car ads carry no price at all; those come back with price_pkr: null and price_on_request: true rather than a zero. A page holds 25 rows on the car and bike surfaces and 24 on the parts surface, and every response repeats page_size, pages and the window the site says it served, so you never have to assume.
Real request and response JSON
Captured from the indexed primary action, car_search, on .
{
"method": "POST",
"url": "https://api.reefapi.com/pakwheels/v1/car_search",
"headers": {
"x-api-key": "$REEF_KEY",
"content-type": "application/json"
},
"body": {
"make": "toyota",
"city": "lahore",
"max_results": 20
}
}{
"ok": true,
"meta": {
"api": "pakwheels",
"endpoint": "car_search",
"mode": "live",
"latency_ms": 1700.5,
"record_count": 25,
"bytes": 580175,
"cache_hit": false,
"price_witness": {
"match": 25,
"mismatch": 0,
"unreadable_display": 0
},
"schema_rows_on_page": 25,
"featured_rows": 4,
"charged_credits": 1,
"version": "0.1.0",
"request_id": "7f619251988346df",
"queue_ms": 1.2
},
"data": {
"total_results": 2538,
"page": 1,
"page_size": 25,
"pages": 102,
"reachable_results": 2538,
"page_window": "1 - 25",
"has_more": true,
"count": 25,
"duplicates_dropped": 0,
"filters_applied": {
"make": "toyota",
"city": "lahore",
"sort": "bumped_at-desc",
"page": 1
},
"search_url": "https://www.pakwheels.com/used-cars/search/-/mk_toyota/rg_lahore/?sortby=bumped_at-desc",
"cars": [
{
"ad_id": "12076090",
"url": "https://www.pakwheels.com/used-cars/toyota-vitz-2008-for-sale-in-peshawar-12076090",
"title": "Toyota Vitz 2008 F 1.3",
"price_pkr": 2265000,
"price_currency": "PKR",
"price_display": "PKR 22.65 lacs",
"price_before_discount_pkr": null,
"price_before_discount_display": null,
"price_on_request": false,
"image_url": "https://cache4.pakwheels.com/ad_pictures/1526/toyota-vitz-1-3-f-2008-152618674.webp",
"images_count": 9,
"featured": false,
"city": "Peshawar",
"brand": "Toyota",
"model": "Vitz",
"managed_by_pakwheels": false,
"updated_text": "Updated about 2 hours ago",
"availability": "InStock",
"variant": "F 1.3",
"year": 2008,
"mileage_km": 109809,
"fuel": "Petrol",
"engine_cc": 1300,
"transmission": "Automatic"
},
{
"ad_id": "12062014",
"url": "https://www.pakwheels.com/used-cars/toyota-corolla-2009-for-sale-in-lahore-12062014",
"title": "Toyota Corolla 2009 Altis Cruisetronic 1.8",
"price_pkr": 2950000,
"price_currency": "PKR",
"price_display": "PKR 29.5 lacs",
"price_before_discount_pkr": null,
"price_before_discount_display": null,
"price_on_request": false,
"image_url": "https://cache3.pakwheels.com/ad_pictures/1524/toyota-corolla-1-8-cruisetronic-2009-152408733.webp",
"images_count": 12,
"featured": true,
"city": "Lahore",
"brand": "Toyota",
"model": "Corolla",
"managed_by_pakwheels": false,
"updated_text": "Updated about 2 hours ago",
"availability": "InStock",
"variant": "Altis Cruisetronic 1.8",
"year": 2009,
"mileage_km": 131452,
"fuel": "Petrol",
"engine_cc": 1800,
"transmission": "Automatic"
},
{
"ad_id": "12076004",
"url": "https://www.pakwheels.com/used-cars/toyota-prius-2017-for-sale-in-lahore-12076004",
"title": "Toyota Prius 2017 S Safety Plus II",
"price_pkr": 6295000,
"price_currency": "PKR",
"price_display": "PKR 62.95 lacs",
"price_before_discount_pkr": null,
"price_before_discount_display": null,
"price_on_request": false,
"image_url": "https://cache3.pakwheels.com/ad_pictures/1526/toyota-prius-s-safety-plus-ii-2017-152617445.webp",
"images_count": 5,
"featured": true,
"city": "Lahore",
"brand": "Toyota",
"model": "Prius",
"managed_by_pakwheels": false,
"updated_text": "Updated about 4 hours ago",
"availability": "InStock",
"variant": "S Safety Plus II",
"year": 2017,
"mileage_km": 103050,
"fuel": "Hybrid",
"engine_cc": 1800,
"transmission": "Automatic"
}
]
}
}What the PakWheels API does
| Action | Description | Concrete use case | Key params |
|---|---|---|---|
| car_search | 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. | Price-intelligence teams call car_search to search the live Pakistani used-car market on pakwheels.com. | query, make, model, city, province, ... |
| car_listing | 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. | Classifieds aggregators call car_listing to get the FULL ad behind a car ad_id. | ad_id |
| bike_search | 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. | Resale and arbitrage tools call bike_search to search the live Pakistani used-motorcycle and scooter market. | query, make, model, city, province, ... |
| bike_listing | 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. | Lead-generation teams call bike_listing to get the FULL ad behind a bike ad_id. | ad_id |
| part_search | 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. | Price-intelligence teams call part_search to search PakWheels' auto-parts and accessories marketplace. | query, sort, page |
| part_listing | 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. | Classifieds aggregators call part_listing to get the full parts/accessory ad behind an ad_id. | ad_id |
| filter_options | 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. | Resale and arbitrage tools call filter_options to get the filter vocabulary this API accepts, group by group, with the label PakWheels itself print…. | none |
Call car_search from your stack
curl -X POST https://api.reefapi.com/pakwheels/v1/car_search \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"make":"toyota","city":"lahore","max_results":20}'import requests
r = requests.post(
"https://api.reefapi.com/pakwheels/v1/car_search",
headers={"x-api-key": REEF_KEY},
json={
"make": "toyota",
"city": "lahore",
"max_results": 20
},
)
print(r.json()["data"])const res = await fetch("https://api.reefapi.com/pakwheels/v1/car_search", {
method: "POST",
headers: {
"x-api-key": process.env.REEF_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
"make": "toyota",
"city": "lahore",
"max_results": 20
}),
});
const { ok, data, meta, error } = await res.json();Ask your MCP-connected assistant: call reefapi.pakwheels.car_search with {"make":"toyota","city":"lahore","max_results":20}.Who uses this API and why
- Price a car against the live Pakistani market: filter by make, model, year band and city, read the exact PKR figures and build your own distribution instead of trusting a dealer's quote.
- Watch a model for arrivals: poll Toyota Corolla in Lahore sorted by newest, keep the ad ids you have seen and alert on the ones you have not.
- Separate dealer stock from private sellers: seller_kind splits the 83,787-ad market into 73,130 private and 10,658 dealer ads, which is the difference between a retail benchmark and a trade one.
- Build an import-vs-local view: the assembly filter splits 60,912 locally assembled cars from 22,872 imports, and the origin filter groups them by Japanese, German, Korean, Chinese and more.
- Track spare-part pricing: search the 58,934-listing parts marketplace by keyword, read the category path and the Buy-Now flag, and follow a part's price and its struck-through old price over time.
Questions developers ask before integrating
How many listings can I actually reach?
All of them. The unfiltered car market measured 83,787 ads over 3,352 pages, and page 3,352 really does serve the last 10 ads (3,351 x 25 + 10 = 83,785 on the run we checked). Asking for a page past the end gets you a clear NOT_FOUND with the page count, not the last page dressed up as page 4,800. Every response states reachable_results alongside total_results.
Prices on PakWheels are written in lacs and crore. What do I get?
An exact integer in rupees, plus the site's own string. A car listed as 'PKR 2.07 crore' comes back as price_pkr: 20700000 and price_display: 'PKR 2.07 crore'. We check the two against each other on every row and report the tally: 375 rows checked on 2026-10-02, 375 agreed, 0 disagreed. If an ad shows a discount we also return the struck-through old price in its own field, so the live price and the old price never get mixed up.
Can I filter to dealers only, or to private sellers only?
Yes, with seller_kind. On 2026-10-02 the market split 73,130 ads from individuals and 10,658 from dealers. Every detail response also carries the seller block: display name, the date they joined PakWheels and their profile image.
Does the API return seller phone numbers?
No, and that is deliberate. The public ad page prints a masked number and the real one only appears after a click, so there is no phone number on the surface we read. We would rather return no field than a field that is the same fake string on every ad. The seller's display name and member-since date are returned.
Do the filters really narrow the results, or are some ignored?
All twenty filters we expose were measured against the unfiltered total in the same run: city 83,787 -> 11,441 for Lahore, make 83,787 -> 22,879 for Toyota, make+model -> 9,993 for Toyota Corolla, automatic -> 48,228, imported -> 22,872, PakWheels-inspected -> 2,688, with video -> 186. The one filter that does not narrow anything is not offered: the city filter is accepted on the parts marketplace and silently ignored there (58,934 before, 58,934 after), so part_search takes keyword, sort and page only.
Are bikes and spare parts the same endpoint as cars?
No, they are separate endpoints because they are genuinely different pages. The bike marketplace publishes no engine size, no gearbox and no stock status on its search rows, so those fields are absent from a bike row instead of returned empty; what it does publish is the engine class, and that is filled on 75 of 75 rows we measured. The parts marketplace publishes no city, no make and no bump time on its rows, and holds 24 rows a page instead of 25.
What happens with a dead or deleted ad id?
NOT_FOUND, not an empty success. PakWheels answers a real 404 for an id that does not exist, and we pass that through. A detail page also shows five similar ads with their own prices, so we match the schema block to the id you asked for and refuse rather than hand you a neighbour's car.
Which filter values are valid?
Call filter_options. It costs nothing and no request, and returns every enum we accept with the label PakWheels itself prints next to it: 20 body types, 7 fuels, 23 colours, 9 origin groups, 8 provinces, 8 sort orders. Make, model and city are open text in the site's own url spelling ('toyota', 'corolla', 'lahore'), so they are listed as open fields rather than invented into a closed list.
What is the PakWheels API?
PakWheels API is a ReefAPI endpoint group for pakistan's car, bike and auto-parts marketplace It returns live JSON through POST requests under /pakwheels/v1.
Is the PakWheels API free to try?
Yes. ReefAPI starts with 1,000 free credits, no card required. PakWheels calls use the same shared credit balance as every other ReefAPI engine.
Do I need a PakWheels login or account?
No login to PakWheels 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 PakWheels data?
The page example is captured from a live car_search call, and production requests fetch live data through ReefAPI rather than a static sample.
How many credits does the PakWheels API use?
PakWheels actions currently cost 1-3 credits per successful call. Failed or blocked calls are free. All APIs draw from one credit pool.
Can I call PakWheels from an AI assistant or MCP client?
Yes. Connect ReefAPI once through MCP and your assistant can call pakwheels actions with the same key, credit pool and JSON envelope used by normal REST requests.