# Mudah.my New Listings & Price Alert (Malaysia) (`superslowsloth/mudah-listings`) Actor

Newest Mudah.my listings for any category, one flat row each: title, price in MYR (null when none is stated), state, town, first-listed and last-bumped times, seller type, photos, make/model/size attributes, URL. Filter by state, keyword, seller type and price.

- **URL**: https://apify.com/superslowsloth/mudah-listings.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.26 / 1,000 listing scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Mudah.my Listings Scraper: New Listings & Price Alert (Malaysia)

Read [Mudah.my](https://www.mudah.my), Malaysia's biggest classifieds site, one
category at a time and get one flat row per listing: title, price in ringgit
(MYR), state and town, when it was first listed and last bumped, seller name
and type, condition, photos, category-specific attributes (car make and
mileage, phone model, bedrooms ...) and the listing URL. Newest first by
default, so a scheduled run is a Mudah.my new-listing alert; price sorts, a
price band, a keyword, a state and a private / business seller filter turn it
into a bargain watch.

No account, no login, no browser. One request returns 39 to 40 listings.

### Input

| Field | Notes |
|---|---|
| `categories` | One or more categories, one per line. Required. A slug as it appears in mudah.my URLs (`cars-for-sale`, `mobile-phones-and-gadgets-for-sale`, `motorcycles-for-sale`, `computers-and-accessories-for-sale`, `properties-for-rent`; `for-sale` is every category), a short name (`cars` becomes `cars-for-sale`), or a **full mudah.my category URL**, which keeps its own state and any filter you set on the website. A listing two categories both show is returned once, under the first. |
| `region` | One state or federal territory as written in Mudah URLs: `selangor`, `kuala-lumpur`, `penang`, `johor`, `sabah` ... Default `malaysia` (everywhere). |
| `query` | Optional keyword searched inside each category (`myvi`, `iphone 15`). |
| `maxItems` | Listings to read **per category** (default 100, up to 10,000). Mudah stops serving a search after about 10,000 results however many it reports. |
| `sort` | `newest` (default), `price-asc`, `price-desc`. |
| `sellerType` | `all` (default), `private`, `company` (business ads). |
| `minPrice`, `maxPrice` | Asking-price band in whole ringgit, inclusive, either end optional. |
| `proxyConfiguration` | Apify proxy by default. |

#### About "newest first"

This is Mudah's own default sort. It orders by when a listing was last placed
at the top, not strictly when it was first posted: a seller who bumps an old
listing sees it jump back. The actor does not re-sort. Every row carries
`first_listed_at`, which is when the listing was originally posted, so filter
on that to keep only genuinely new listings.

#### About the price sorts and the price band

Both are applied by Mudah. A price band also drops listings with no price.
`price-asc` starts with RM 1 listings (9 of the first 12 rows on one measured
sort), most of them placeholders for free or negotiable items; add a
`minPrice` of 2 or more to skip them.

### Output

| Field | Notes |
|---|---|
| `listing_id`, `url` | Mudah's listing id (the number at the end of the URL) and the absolute listing URL. |
| `search_category` | The category slug that found the row. |
| `title`, `description` | As the seller wrote them. `description` is filled on the general category pages (phones, computers, motorcycles, furniture, fashion, pets ...) and is **null on almost every car and property listing**, where Mudah's page ships the text as a deferred reference this actor does not follow (an occasional listing carries it inline, and then it is returned). |
| `price_myr`, `old_price_myr`, `price_suffix` | See below. |
| `category`, `category_id`, `parent_category`, `ad_type` | Mudah's category name and id, the group it sits under where Mudah sends one, and `sell` / `let` / `want` as Mudah words it. |
| `region`, `region_id`, `area`, `area_id` | State and town. |
| `listed_at`, `first_listed_at`, `bumped_at` | See below. |
| `seller_name`, `seller_type`, `seller_id`, `store_id`, `seller_verified` | The display name, `company` or `private`, Mudah's user id and (for business sellers) shop id, and the shop's verified flag. |
| `condition` | `New`, `Used`, `Like New` ... as Mudah words it; null for property and rentals. |
| `image_url`, `image_urls`, `image_count` | The main photo, every photo the page lists (absolute URLs, nothing is downloaded), and Mudah's own count. |
| `attributes` | One object of category-specific fields. Cars: `make`, `model`, `body_type`, `year`, `transmission`, `fuel_type`, `engine_cc`, `mileage_km` (a band such as `90000-94999`), `mudah_certified`. Phones: `brand`, `model`, `warranty`. Motorcycles: `make`, `model`, `year`. Property: `property_type`, `tenure`, `bedrooms`, `bathrooms`, `size`, `size_unit`, `building`. Only keys the listing carries are present. |
| `page`, `position` | The result page the row came from and Mudah's own rank on it (null on property pages, which carry none). |

#### The three timestamps

Mudah sends three times and they mean different things. All are Malaysia time
(+08:00) in ISO 8601.

| Field | Meaning |
|---|---|
| `listed_at` | Mudah's `origListTime`. Sent only for a listing Mudah has re-listed or bumped; **null for most rows** (measured: null on 358 of 400 listings across ten general-category pages and on 5 of 39 on a cars page, and absent on many property listings). It is never filled in from another field. |
| `first_listed_at` | Mudah's `origDate`: when the listing was first placed. It equals `listed_at` whenever that is set and equals `bumped_at` when it is not, on all 838 listings measured across 23 category pages (225 with `listed_at`, 613 without), so it is the posting time either way. Use this one for a new-listing alert. |
| `bumped_at` | When Mudah last placed the listing at the top: its `date`, the time the website shows and the time it sorts by. Moves when the seller bumps. It is not the last edit (`modifiedAt` ran later than `date` on 34 of 743 measured listings), and it is not returned. |

#### The price, honestly

`price_myr` is the asking price in ringgit as a number, always a float. For a
rental it is the **monthly rent** (Mudah sends it in a different field) and
`price_suffix` says `per month`. It is **null** when a listing carries no
positive amount. `old_price_myr` is the earlier price Mudah shows struck
through, otherwise null; it is usually above the current price but not always
(one of five measured motorcycles had a lower one), so it is not a guaranteed
markdown. Listings priced RM 1 are returned as RM 1, not hidden or turned into null.

#### Honest nulls

A field Mudah did not send is `null`, never `0` or `""`: `description` on most car
and property listings, `condition` on property, `seller_verified` and `store_id` for
private sellers without a shop, `position` on property pages,
`parent_category` on car and property pages. `image_urls` is an empty list when
the listing has no photo, and `attributes` an empty object when the category
sends none.

#### What is not a row

Mudah pins paid "featured" listings above the results. They are a separate
list in the page and are **not** returned, so every row is an organic result
in Mudah's own order.

#### Personal data

Listing text and display names are written by private individuals and
businesses and can hold names or contact details. They are carried as Mudah
publishes them, because they are the listing; nothing is extracted from them.
The actor does not read or return phone numbers. You are responsible
for handling what you store under Malaysia's PDPA.

### Pricing

Pay per event:

- **$0.002** per run (`actor-start`), charged only after your input is
  validated - a run that fails on bad input costs nothing.
- **$0.00126** per listing row written (`listing-scraped`), i.e. $1.26 per
  1,000 listings.

### How it fetches

It requests the same category pages the website shows
(`www.mudah.my/<state>/<category>?o=<page>`) and reads the listing data Mudah
embeds in them. Two page families exist and both are read: the general
categories carry a `__NEXT_DATA__` JSON, and cars and property carry a React
Server Components stream. Measured 2026-10-10: HTTP 200 from Apify datacenter
and residential exits and from a plain connection. Apify datacenter proxy is
the default.

Things worth knowing, all measured:

- **Cloudflare challenge.** After roughly 40 page requests in a few minutes
  from one address Mudah answers `200` with Cloudflare's "Just a moment" page
  and no listings (it cleared within minutes). The actor treats a 200 without
  listing data, and 403, 429 and 5xx, as a refusal: it rotates to a fresh proxy
  address and retries.
- **Result cap.** Mudah serves at most about 10,000 results per search (the last
  answered page was 250 for the general categories and 256 for cars) and answers
  beyond it with an empty page; the run logs when a category was cut off that way.
- **A misspelt category is not an error on Mudah's side.** It redirects an
  unknown category slug to a keyword search of every category. The actor
  notices that the page is not the category you asked for and reports a failure
  instead of returning unrelated listings. An unknown state is answered with an
  empty "no ads" page and is reported the same way.
- **Keyword and filters are applied by Mudah**, on both page families:
  `query`, `sort`, `sellerType` and the price band were each checked against
  live pages.

# Actor input Schema

## `categories` (type: `array`):

Mudah.my categories to read, one per line: a category slug as it appears in mudah.my URLs (cars-for-sale, mobile-phones-and-gadgets-for-sale, motorcycles-for-sale, computers-and-accessories-for-sale, properties-for-rent, for-sale for all categories), a short name like cars (becomes cars-for-sale), or a full mudah.my category URL with any filters you set on the website (it keeps its own state and filters). A listing that two categories both show is returned once, under the first.

## `region` (type: `string`):

Limit the search to one state or federal territory, written as in Mudah URLs: selangor, kuala-lumpur, penang, johor, perak, sabah, sarawak, melaka, kedah, negeri-sembilan and so on. Leave as malaysia for the whole country. An unknown value makes Mudah answer with an empty page and the run reports it as a failure. Ignored for a category given as a full URL, which keeps its own state.

## `query` (type: `string`):

Optional keyword searched inside each category (for example myvi or iphone 15). Mudah matches it against the listing title. Leave empty to read the whole category.

## `maxItems` (type: `integer`):

Read at most this many listings for each category. Mudah serves 39 to 40 per page and stops serving a search after about 10,000 results (page 250 or 256), however many it reports finding.

## `sort` (type: `string`):

The order Mudah lists the results in. 'Newest first' is Mudah's own default and is the one for new-listing alerts; it orders by when a listing was last placed at the top, so a listing the seller bumps can reappear near the top. Use first_listed_at to tell a new listing from a bumped one.

## `sellerType` (type: `string`):

Return every listing, only private sellers, or only business sellers (Mudah's 'company' ads).

## `minPrice` (type: `integer`):

Lowest asking price to return, in whole Malaysian ringgit, inclusive. Leave empty for no lower bound. For rentals the price is the monthly rent.

## `maxPrice` (type: `integer`):

Highest asking price to return, in whole Malaysian ringgit, inclusive. Leave empty for no upper bound. For rentals the price is the monthly rent.

## `proxyConfiguration` (type: `object`):

Apify proxy (automatic datacenter exits) is the default and is what this actor was measured on: Mudah's category pages answered Apify datacenter and residential exits with HTTP 200 on 2026-10-10. A proxy is what lets a refused request, such as Cloudflare's challenge page, be retried from a fresh address.

## Actor input object example

```json
{
  "categories": [
    "cars-for-sale"
  ],
  "region": "malaysia",
  "maxItems": 100,
  "sort": "newest",
  "sellerType": "all",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "categories": [
        "cars-for-sale"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/mudah-listings").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "categories": ["cars-for-sale"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/mudah-listings").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "categories": [
    "cars-for-sale"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call superslowsloth/mudah-listings --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,superslowsloth/mudah-listings"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/3JShj8EY1WxehANZy/builds/MJQpejfZUXaF3a38z/openapi.json
