Mudah.my API & Scraper
The Mudah API returns Malaysia's largest classifieds marketplace as clean JSON in eight actions.
🤖 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.
search takes a keyword, a category, a state and a town, and returns up to 200 ads a call across every vertical mudah runs: cars, motorcycles, number plates, property for sale and to rent, mobile phones, TV and audio, computers, furniture, home appliances, clothes, sports, pets, food, business equipment, jobs and services. Every row carries the listing id and URL, the title, the price in MYR with price_kind saying whether that number is a sale price, a monthly rent or a salary, price_label exactly as mudah prints it, condition, ad_type, the category and its group, the state and town, posted_at and bumped_at, the cover photo, whether the seller is a private person or a business, and a details block holding whatever the category itself publishes: make, model, mileage, transmission and manufactured year on a car, bedrooms, bathrooms and floor size on a property, brand, model, storage and warranty on a phone. listing opens one ad in full: the complete description, every photo in standard and high resolution, the attribute table, the vehicle specifications block, the shop identity and registration number where one is published, and how buyers reach the seller. seller_listings returns a dealer's, an agency's or a shop's whole stock by store_id, or a private account by user_id, in the same row shape, 200 to a page. similar returns mudah's own comparable live ads for one listing. suggest is mudah's keyword autocomplete, each suggestion carrying the category it belongs to and ready-made filter values. categories, locations and filters cost nothing: the whole category tree, 16 states and federal territories with their towns, and one category's complete filter schema with every allowed value. That last one earns its place, because mudah accepts a filter key it does not know and answers with the unfiltered set: rooms=3 left all 104,169 property ads in place, and price_min with price_max returned zero rows. Every filter sent here is resolved against mudah's own published schema and rejected with the allowed values when it is not in it. 24 filters were run against a same-minute unfiltered control and all 24 narrowed: make=Toyota cut 89,804 car ads to 19,549 and adding model=Hilux cut it to 1,051 with 40 of 40 rows Hilux, a 1,000 to 2,000 MYR band cut phones from 13,484 to 3,415 with 40 of 40 in range, selangor cut them to 3,042 and Petaling Jaya inside it to 199. 171 live calls across all eight actions returned 100 per cent. No mudah account, one ReefAPI key, and the standard { ok, data, meta, error } envelope.
Six Mudah values that are not what they look like
Mudah answers a bad request with a plausible page rather than an error: a filter key it does not know comes back unfiltered, a page size it does not like comes back at its own default, and the end of its search window comes back as zero results. These were read from live searches and listing pages on 2026-09-24.
| What you send or read | Looks like | Actually is | What comes back |
|---|---|---|---|
| price | a sale price in MYR | a sale price, a monthly rent or a job salary, all written into the same field | the number in price, plus price_kind reading price, rent or salary, and price_label exactly as mudah prints it |
| price on a services ad | a parse gap | mudah publishes no price for that vertical: 200 of 200 service ads read Negotiable | price null and price_not_published true, never 0 |
| rooms=3 on property | a filter | a key mudah does not recognise, so it serves the unfiltered 104,169 ads | rejected before anything is sent, with the keys and values mudah does publish; the key that narrows takes the same search to 67,675 |
| limit=250 | 250 rows | mudah's own default of 24 rows, with no error and no clamp | capped at 200, which is the real maximum |
| offset past 10,000 | an empty market | the end of mudah's search window: it reports 0 total results for a category holding tens of thousands of ads | refused, with a message to narrow the query rather than page deeper |
| condition on a property, job, service, pet, food or number-plate ad | a missing field | the category defines no condition at all, in its rows and in its filter schema alike | absent, and said to be absent rather than filled in |
Measured 2026-09-24 across 17 categories at 40 rows each and 40 listing pages. A sold or expired ad is not served at all, so a listing id that no longer exists comes back NOT_FOUND rather than as an empty success, and there is no sold-price history to read.
Real request and response JSON
Captured from the indexed primary action, search, on .
{
"method": "POST",
"url": "https://api.reefapi.com/mudah/v1/search",
"headers": {
"x-api-key": "$REEF_KEY",
"content-type": "application/json"
},
"body": {
"category": "1020",
"limit": 20
}
}{
"ok": true,
"meta": {
"api": "mudah",
"endpoint": "search",
"mode": "live",
"latency_ms": 1768.1,
"record_count": 20,
"bytes": 60272,
"cache_hit": false,
"pagination": {
"has_more": true,
"page_size": 20,
"total": 88929
},
"upstream_requests": 2,
"charged_credits": 1,
"version": "0.1.0"
},
"data": {
"results": [
{
"listing_id": 113704677,
"ad_id": 133603631,
"title": "Mini Cooper 2.0 SD Edisi 60 Thn (A) Fulloan Malay",
"url": "https://www.mudah.my/mini-cooper-2-0-sd-edisi-60-thn-a-fulloan-malay-113704677.htm",
"price": 99999,
"price_label": "RM 99,999",
"price_kind": "price",
"price_not_published": false,
"original_price": 105000,
"currency": "MYR",
"condition": "Used",
"condition_id": "1",
"ad_type": "sell",
"category": {
"id": "1020",
"name": "Cars",
"group_id": null,
"group_name": null
},
"location": {
"region_id": "8",
"region": "Selangor",
"area_id": "312",
"area": "Petaling Jaya"
},
"posted_at": "2026-08-29T05:08:49+08:00",
"bumped_at": "2026-09-26T05:08:49+08:00",
"updated_at": "2026-09-26T05:10:01+08:00",
"expires_at": "2026-10-30T05:08:49+08:00",
"description": null,
"image": "https://img.rnudah.com/images/29/2966157366585831308.jpg",
"images": [
"https://img.rnudah.com/images/29/2966157366585831308.jpg"
],
"image_grid": "https://img.rnudah.com/grids/29/2966157366585831308.jpg",
"image_count": 16,
"seller": {
"user_id": 4317614,
"name": "MS ALLIANCES",
"type": "pro",
"is_business": true,
"business_evidence": [
"[trimmed-depth]",
"[trimmed-depth]"
],
"store_id": 644313,
"store_verified": "verified",
"pii_withheld": false
},
"details": {
"model_name": "Cooper",
"transmission_name": "Auto",
"model_id": "1001",
"make_name": "Mini",
"car_loan_eligible": true,
"engine_capacity": "1995",
"mileage": {
"lte": "[trimmed-depth]",
"gte": "[trimmed-depth]"
},
"car_type_id": "4",
"manufactured_year": "2020",
"bundle": "46",
"car_type_name": "Hatchback",
"has_car_grant": true,
"year_verified": true,
"make_id": "21",
"car_loan_tenure": 7,
"fueltype": "petrol",
"year_verified_highlight": "#DDEFFD",
"categorised_badges": {
"seller": "[trimmed-depth]",
"ad": "[trimmed-depth]"
},
"ad_highlights": {
"data": "[trimmed-depth]"
},
"location": "Selangor - Petaling Jaya"
}
},
{
"listing_id": 115098396,
"ad_id": 135141688,
"title": "Peugeot 208 Allure(A) FullLoan Blacklist LoanKedai",
"url": "https://www.mudah.my/peugeot-208-allure-a-fullloan-blacklist-loankedai-115098396.htm",
"price": 12500,
"price_label": "RM 12,500",
"price_kind": "price",
"price_not_published": false,
"original_price": null,
"currency": "MYR",
"condition": "Used",
"condition_id": "1",
"ad_type": "sell",
"category": {
"id": "1020",
"name": "Cars",
"group_id": null,
"group_name": null
},
"location": {
"region_id": "8",
"region": "Selangor",
"area_id": "312",
"area": "Petaling Jaya"
},
"posted_at": "2026-08-29T05:01:05+08:00",
"bumped_at": "2026-09-26T05:01:05+08:00",
"updated_at": "2026-09-26T05:05:01+08:00",
"expires_at": "2026-10-30T05:01:05+08:00",
"description": null,
"image": "https://img.rnudah.com/images/29/2989921352022640523.jpg",
"images": [
"https://img.rnudah.com/images/29/2989921352022640523.jpg"
],
"image_grid": "https://img.rnudah.com/grids/29/2989921352022640523.jpg",
"image_count": 16,
"seller": {
"user_id": 4317614,
"name": "MS ALLIANCES",
"type": "pro",
"is_business": true,
"business_evidence": [
"[trimmed-depth]",
"[trimmed-depth]"
],
"store_id": 644313,
"store_verified": "verified",
"pii_withheld": false
},
"details": {
"car_type_name": "Hatchback",
"manufactured_year": "2014",
"make_name": "Peugeot",
"year_verified": true,
"model_name": "208",
"fueltype": "petrol",
"mileage": {
"lte": "[trimmed-depth]",
"gte": "[trimmed-depth]"
},
"car_loan_eligible": true,
"car_loan_tenure": 7,
"bundle": "45",
"car_type_id": "4",
"has_car_grant": true,
"transmission_name": "Auto",
"make_id": "26",
"engine_capacity": "1598",
"model_id": "4040",
"year_verified_highlight": "#DDEFFD",
"categorised_badges": {
"seller": "[trimmed-depth]",
"ad": "[trimmed-depth]"
},
"ad_highlights": {
"data": "[trimmed-depth]"
},
"location": "Selangor - Petaling Jaya"
}
},
{
"listing_id": 115098395,
"ad_id": 135143048,
"title": "Toyota Unser 1.8 GLI (M) Loan Kedai Blacklist TTop",
"url": "https://www.mudah.my/toyota-unser-1-8-gli-m-loan-kedai-blacklist-ttop-115098395.htm",
"price": 15000,
"price_label": "RM 15,000",
"price_kind": "price",
"price_not_published": false,
"original_price": null,
"currency": "MYR",
"condition": "Used",
"condition_id": "1",
"ad_type": "sell",
"category": {
"id": "1020",
"name": "Cars",
"group_id": null,
"group_name": null
},
"location": {
"region_id": "8",
"region": "Selangor",
"area_id": "312",
"area": "Petaling Jaya"
},
"posted_at": "2026-08-29T05:01:02+08:00",
"bumped_at": "2026-09-26T05:01:02+08:00",
"updated_at": "2026-09-26T05:05:01+08:00",
"expires_at": "2026-10-30T05:01:02+08:00",
"description": null,
"image": "https://img.rnudah.com/images/29/2989935199985158027.jpg",
"images": [
"https://img.rnudah.com/images/29/2989935199985158027.jpg"
],
"image_grid": "https://img.rnudah.com/grids/29/2989935199985158027.jpg",
"image_count": 14,
"seller": {
"user_id": 4317614,
"name": "MS ALLIANCES",
"type": "pro",
"is_business": true,
"business_evidence": [
"[trimmed-depth]",
"[trimmed-depth]"
],
"store_id": 644313,
"store_verified": "verified",
"pii_withheld": false
},
"details": {
"car_type_name": "MPVs",
"car_loan_eligible": false,
"mileage": {
"gte": "[trimmed-depth]",
"lte": "[trimmed-depth]"
},
"make_name": "Toyota",
"transmission_name": "Manual",
"model_id": "1729",
"car_type_id": "5",
"engine_capacity": "1781",
"year_verified": true,
"has_car_grant": true,
"bundle": "45",
"car_loan_tenure": 7,
"fueltype": "petrol",
"make_id": "35",
"manufactured_year": "2004",
"model_name": "Unser",
"year_verified_highlight": "#DDEFFD",
"categorised_badges": {
"seller": "[trimmed-depth]",
"ad": "[trimmed-depth]"
},
"ad_highlights": {
"data": "[trimmed-depth]"
},
"location": "Selangor - Petaling Jaya"
}
}
],
"count": 20,
"total_results": 88929,
"total_is_capped": true,
"offset": 0,
"limit": 20,
"has_more": true,
"next_offset": 20,
"count_by_seller_type": null,
"corrected_keyword": null,
"filters_applied": {
"category": {
"id": "1020",
"name": "Cars"
},
"sort": "newest"
},
"sort_applied": "newest",
"query": null,
"category": {
"id": "1020",
"name": "Cars"
},
"currency": "MYR",
"pii_included": true,
"empty_confirmed": null
}
}What the Mudah.my API does
| Action | Description | Concrete use case | Key params |
|---|---|---|---|
| search | Search Mudah.my across every vertical — cars, motorcycles, property for sale and to rent, phones and electronics, home appliances, furniture, fashion, sports, pets, food, jobs and services — by keyword and/or category, up to 200 ads per call. Each row carries the listing id and URL, title, price in MYR, condition, state and town, when it was posted and last bumped, the cover photo, whether the seller is a private person or a business, and every vertical-specific field mudah publishes (car make/model/mileage/transmission/year, property bedrooms/bathrooms/size, phone brand/model/storage…). Filter by price range, condition, state, town, ad type and seller type, plus any filter the category itself publishes — those are validated against mudah's own schema, so a filter that mudah would quietly ignore is rejected instead. | Pricing teams call search to search Mudah.my across every vertical. | query, category, region, area, price_min, ... |
| listing | One ad in full, by its list id or URL: title, complete description, price and the seller's earlier price, condition, category, state and town, every photo in standard and high resolution, every attribute the category defines (car make, model, variant, engine, mileage, transmission and manufactured year; property type, tenure, bedrooms, bathrooms, floor size, facilities, carpark; phone brand, model, storage and warranty; job salary, contract type, experience, education, languages and company details), the dealer's registration number where one is published, shop identity and verification, and how buyers can reach the seller. | Marketplace operators call listing to get one ad in full, by its list id or URL. | listing_id, include_pii |
| seller_listings | Every live ad of one seller — a private account by user id, or a dealer / agency / shop by store id — with the same row shape as search, so a dealer's whole stock or a shop's whole catalogue comes back in pages of up to 200. | Catalog enrichment teams call seller_listings to get every live ad of one seller. | store_id, user_id, query, category, region, ... |
| similar | Mudah's own 'similar ads' for one listing — the comparable live ads it shows next to it, with price, condition highlights, location and photo. Useful for pricing a used item against what the same market is asking right now. | Retail analysts call similar to get mudah's own 'similar ads' for one listing. | listing_id, include_pii |
| suggest | Mudah's keyword autocomplete: what real buyers type, each suggestion carrying the category it belongs to and the ready-made filter values for it — so a half-typed word becomes a valid search without guessing a category id. | Pricing teams call suggest to get mudah's keyword autocomplete. | query |
| categories | Mudah's whole category tree — every group, category and sub-category with its id, name and slug. Free: these ids are what `search` and `filters` take, so nobody has to guess one. | Marketplace operators call categories to get mudah's whole category tree. | none |
| locations | Every Malaysian state and federal territory mudah lists, each with its towns and districts, ids and slugs. Free: these are the values `region` and `area` take. | Catalog enrichment teams call locations to get every Malaysian state and federal territory mudah lists, each with its towns and districts, i…. | none |
| filters | Every filter one category publishes, with its key, label, kind (single, multi or range) and the complete list of allowed values — car makes and models, property types and facilities, phone brands, models and storage sizes, job categories and contract types, conditions, sort orders. Free, and the authoritative input for `search`'s `filters` parameter. | Retail analysts call filters to get every filter one category publishes, with its key, label, kind (single, multi or range) and t…. | category |
Call search from your stack
curl -X POST https://api.reefapi.com/mudah/v1/search \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"category":"1020","limit":20}'import requests
r = requests.post(
"https://api.reefapi.com/mudah/v1/search",
headers={"x-api-key": REEF_KEY},
json={
"category": "1020",
"limit": 20
},
)
print(r.json()["data"])const res = await fetch("https://api.reefapi.com/mudah/v1/search", {
method: "POST",
headers: {
"x-api-key": process.env.REEF_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
"category": "1020",
"limit": 20
}),
});
const { ok, data, meta, error } = await res.json();Ask your MCP-connected assistant: call reefapi.mudah.search with {"category":"1020","limit":20}.Who uses this API and why
- Price a used car, phone or condo in Malaysia against what the market is asking this week, by category, make and state.
- Track one dealer, agency or shop: pull its entire live stock by store_id and diff it daily to see what listed and what went.
- Measure advertised rents by town or state from ad_type=let property rows, with bedrooms, bathrooms and floor size on each.
- Feed a buyer-alert or comparison product: filter by category, price band and condition, sort by newest, and de-duplicate on listing_id.
- Size a Malaysian segment before entering it, using total_results per filter combination as the market count.
Questions developers ask before integrating
How do I tell Mudah what to search?
Any combination of a keyword and a place. query is the keyword, category is a category id, slug or name such as 1020, cars or Cars, region is a state or federal territory such as selangor or Kuala Lumpur, and area is a town or district inside it, where a comma list is allowed. A bare category with no keyword browses everything in it, and a parent category id includes its children. You do not have to guess any of those ids: categories and locations are free actions that list every one.
Do the Mudah filters actually filter?
They do here, and that is the point of the action. Mudah itself accepts a filter key it does not know and quietly returns the unfiltered set: rooms=3 left all 104,169 property ads in place, property_type, job_cat, contract_type, pet_type and gender_type did the same, and price_min with price_max went the other way and returned nothing at all. Every filter you send is resolved against mudah's own published schema first and rejected with the allowed values when it is not there. 24 filters were then run against a same-minute unfiltered control and all 24 narrowed: make=Toyota took 89,804 car ads to 19,549 and model=Hilux on top of it to 1,051 with 40 of 40 rows Hilux, condition=new took phones from 13,484 to 1,534 with 40 of 40 New, ad_type=let took property from 104,171 to 46,683 with 40 of 40 let, and seller_type private and company split 13,484 into 12,290 and 1,194 which add back to the total.
How many Mudah ads can one search return?
Up to 200 a call, and that ceiling is enforced because mudah does not enforce it: ask for 201 or more and it serves 24 rows with no error. Page with offset, and keep offset plus limit within 10,000, which is mudah's search window. Past it mudah reports zero total results for a category holding tens of thousands of ads, so a deeper request is refused here instead of being answered with that lie. total_results tells you how large the market really is, so partitioning by state, town, make or price band is how you reach the rest.
What does the price field mean on a rental or a job ad?
Mudah writes a sale price, a monthly rent and a job salary into the same field, so every row says which one it is in price_kind, reading price, rent or salary. Where an ad publishes no price at all mudah shows Negotiable, and price comes back null with price_not_published true rather than 0, because a zero there would read as free. Measured on 1,800 rows across nine categories: price is present on 40 of 40 rows in 15 of the 17 categories tested, 39 of 40 in jobs, and 0 of 40 in services, where all 200 sampled ads carried Negotiable.
Do I get the seller's phone number?
For a dealer, an agency or a shop, yes, always: those are business facts, and the shop identity, its verification and its registration number where one is published come with them. For a private individual the name and number are off by default, because on a classifieds site those belong to a person. You still see that a number exists, as a masked phone_masked, and seller.pii_withheld says plainly that it was withheld rather than absent. Set include_pii to true when you have a lawful reason and the full contact comes back. Every listing also publishes seller.business_evidence, so the private-or-business call is auditable rather than a hidden rule.
Is there a price per square foot, or what an item finally sold for?
No to both, and neither is invented. Mudah publishes no unit price anywhere, so there is no price per square foot on a property and no price per unit on anything else, and none is worked out here: what you get is the advertised price and, for property, the floor size, as two separate fields. There is also no sold price. Mudah stops serving an ad entirely when it goes, so a dead listing id is NOT_FOUND and there is no record of what it closed at.
Which Mudah fields are missing, and why?
Whatever the category does not publish, and it is left absent rather than guessed. listing_id, title, url, the cover image, state, town and posted_at came back on 40 of 40 rows in all 17 categories tested. Condition is not defined at all for property, jobs, services, pets, food and number plates. The vertical details block is full on cars, motorcycles, property, phones, clothes, pets and jobs, thin on business equipment at 16 of 40 and on number plates and food at 3 of 40, and empty on furniture and home appliances, which publish no extra row fields. Job ads often have no photo, 24 of 40. On the 40 listing pages measured, description and contacts were 40 of 40, images and the attribute table 38 of 40, and the specifications block 4 of 40, because only vehicles have one.
What happens when a Mudah search finds nothing?
It is checked before it is reported. A genuine no-match and a throttled read look identical from the outside, both a plain empty list, so an empty search is asked a second time and only reported empty when both reads agree. The answer then carries empty_confirmed true, which means mudah really has no ad matching the query.
What is the Mudah.my API?
Mudah.my API is a ReefAPI endpoint group for malaysia's largest classifieds: cars, property for sale and to rent, phones, appliances, fashion, pets, jobs and services, priced in myr. It returns live JSON through POST requests under /mudah/v1.
Is the Mudah.my API free to try?
Yes. ReefAPI starts with 1,000 free credits, no card required. Mudah.my calls use the same shared credit balance as every other ReefAPI engine.
Do I need a Mudah.my login or account?
No login to Mudah.my 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 Mudah.my 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 Mudah.my API use?
Mudah.my actions currently cost 1 credit per successful call. Failed or blocked calls are free. All APIs draw from one credit pool.
Can I call Mudah.my from an AI assistant or MCP client?
Yes. Connect ReefAPI once through MCP and your assistant can call mudah actions with the same key, credit pool and JSON envelope used by normal REST requests.