# Mubawab Property Alerts: Real Estate Listing Monitor (`accountable_eel/mubawab-property-alerts`) Actor

Mubawab real estate monitor for Morocco: new sale listings and price drops by city, such as Casablanca, Marrakech, Agadir and Tanger. Mubawab listings for sale under a max price. Export Mubawab listings to Google Sheets or n8n. Morocco only.

- **URL**: https://apify.com/accountable_eel/mubawab-property-alerts.md
- **Developed by:** [Adriana Voss](https://apify.com/accountable_eel) (community)
- **Categories:** Real estate, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 mubawab listing returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Mubawab Property Alerts: New Moroccan Listings and Price Drops by City

Give it a Moroccan city and it reads the first page of Mubawab's for-sale results for that city, one row per listing with price, area and a link. Switch on monitoring and later runs return only the listings that appeared since last time, plus any whose asking price fell.

### Who it's for

**A Moroccan living abroad who is hunting for an apartment back home.** You are in Montreal or Lyon, you cannot refresh Mubawab at Casablanca business hours, and you worry that the good flats in a given neighbourhood will be gone before you see them. You want a message when a new one lists, with the price already in dirhams.

**A small investor following price movement in Agadir or Marrakech.** You are not after every listing, only the ones that get cheaper. A listing that drops 8% after three weeks on the market is a negotiation signal, and the price-drop alert exists for exactly that.

**A relocation consultant with several clients in different cities.** Each client has a city and a budget. You run one scheduled search per client, set a `maxPrice`, and send each client their own new listings.

### Why this one

- **Prices in the row.** Price and currency are separate fields, so you can sort and filter without parsing a string such as "1 825 000 DH".
- **Two kinds of alert.** New listings, and price drops above a threshold you set (5% by default).
- **A budget filter.** `minPrice` and `maxPrice` cut the page before anything is billed.
- **Billed per listing returned.** An empty search costs nothing beyond the run start fee.

### What you get

One row per listing on the city's first results page.

| Field | Meaning |
| --- | --- |
| `listingId` | The numeric id from the listing link. |
| `title` | The listing headline, for example "Apartment for sale in Oasis. Area 70 m²". |
| `price`, `currency` | Asking price as a number, and its currency (`MAD` unless the page shows another). |
| `area` | Floor area in square metres, when the card shows it. |
| `bedrooms`, `bathrooms` | Counts when the card states them. |
| `url` | The listing page. |
| `isNew`, `changeType` | In monitoring mode: `new`, `price_drop` or `seen`. |
| `previousPrice`, `priceDropPct` | Filled on a price drop. |
| `firstSeen`, `lastSeen` | When this monitor first and last saw the listing. |

**What was filled in our tests.** We saved the Casablanca results page on 2026-10-08 and parsed it: 31 listings, and every one had a price, an area, a location, an image and a description. Bedrooms and bathrooms were present on 30 of the 31. Live pages for other cities that day carried a few "price on request" cards, so expect `price` to be empty on roughly one listing in ten, and treat every other column as filled.

### Which Moroccan cities work, and what happens in Tunisia

The city you type becomes the address `www.mubawab.ma/en/ct/<city>/real-estate-for-sale`, with spaces turned into hyphens. We tried it on 2026-10-08:

| City you type | Mubawab page | Listings parsed |
| --- | --- | --- |
| Casablanca | loads | 31 |
| Rabat | loads | 32 |
| Marrakech | loads | 31 |
| Agadir | loads | 33 |
| Tanger | loads | 31 |
| Meknes | loads | 33 |
| El Jadida | loads | 32 |
| Essaouira | loads | 32 |
| Oujda | loads | 33 |
| Nador | loads | 33 |
| Temara | loads | 32 |
| Salé (typed with the accent) | loads | 32 |
| Fes, Kenitra, Mohammedia, Tetouan, Laayoune | not found (404) | 0 |
| Tangier (English spelling), Sale (no accent) | not found (404) | 0 |

A results page holds about 32 listings. Two lessons follow. Use the spelling Mubawab uses in its own address: it is `Tanger`, not `Tangier`, and `Salé` with its accent. And do not combine a property type with a city, because "Apartment Marrakech" becomes the address `apartment-marrakech`, which does not exist. The counts above were measured against the live site on 2026-10-08. A city that Mubawab does not list under the spelling you typed still ends without an error: you get one row with `listingCount: 0`, and nothing is billed.

**Tunisia.** This actor covers Morocco only. It requests only `www.mubawab.ma`, and when we typed `tunis` and `sousse` there on 2026-10-08 both returned 404, so Tunisian cities do not work.

Only the first page of the for-sale section is read. Rentals are not covered.

### Pricing

- **Mubawab listing returned**: $2 per 1,000 listings

Plus a $0.003 start fee per run. Each event above is billed independently, only when it actually returns data — misses (`found:false`) are never charged.

A full first page of about 32 listings costs roughly six or seven cents. In monitoring mode a later run bills only the new or reduced listings it returns, so a quiet day costs close to nothing.

### How to use

1. **In the Apify Console.** Open the actor page and click **Start** — the `searches` field is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found.
2. **Via the API.** Call it directly with a POST request — no Console needed once you have an API token:
   ```bash
   curl "https://api.apify.com/v2/acts/accountable_eel~mubawab-property-alerts/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"searches":["Casablanca"]}'
   ```
3. **On a schedule.** Save this actor as an Apify **Task** with the input you want, then add a **Schedule** (hourly, daily, weekly) so it runs on its own — no server of your own required.

For monitoring, switch on `deltaMode` and put the actor on a daily schedule. Add `skipFirstRun` if you want the first run to only record what is currently listed.

### Input

```json
{
  "searches": [
    "Casablanca"
  ]
}
```

One Moroccan city per line, spelled as in Mubawab addresses. E.g. "Casablanca", "Marrakech", "Agadir". Accepted formats: Casablanca, Marrakech, Agadir.

Other settings: `city` (overrides the address with an exact city name and is echoed into the `city` column), `minPrice` and `maxPrice` (in dirhams; listings with no price are dropped when either is set), `alertOnNew`, `alertOnPriceDrop`, `minPriceDropPct`, `deltaMode`, `deltaName`, `skipFirstRun`, `includeKeywords` and `excludeKeywords` (matched against the row text), and `maxResults`. The `propertyType` setting is written into the rows but does not change which page is read; put words such as "villa" in `includeKeywords` to narrow instead.

### Sample output

| query | found | status | searchQuery | listingCount | newCount | priceDropCount | listings | listingId | title | description | price | currency | city | location | propertyType | area | bedrooms | bathrooms | url | imageUrl | isNew | changeType | previousPrice | priceDropPct | firstSeen | lastSeen | scrapedAt |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Casablanca | true | OK | <search query> | <listings returned> | <new listings> | <price drops> | <listings> | <listing id> | <title> | <description> | <price> | <currency> | <city> | <location> | <property type> | \<area (m²)> | <bedrooms> | <bathrooms> | <listing url> | <image url> | <is new> | <change type> | <previous price> | \<price drop %> | <first seen> | <last seen> | 1970-01-01T00:00:00.000Z |

### Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.

```bash
curl "https://api.apify.com/v2/acts/accountable_eel~mubawab-property-alerts/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"searches":["Casablanca"]}'
```

**n8n.** Add an HTTP Request node: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~mubawab-property-alerts/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body Content Type `JSON`, JSON Body `{"searches":["Casablanca"]}` (swap in an expression from an earlier node for a real value).

**Clay.** Add an "HTTP API" column: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~mubawab-property-alerts/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body `{"searches":["{{search}}"]}`, mapping the row's search into the `searches` array.

**MCP.** In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "Mubawab Property Alerts | Apify" — the agent will find and run this actor.

### Tips

- **Test the city first.** Open `mubawab.ma/en/ct/<city>/real-estate-for-sale` in a browser. If that page is a 404, the actor returns a zero-listing row and charges nothing.
- **Pick a high-turnover city for alerts.** Casablanca and Marrakech return a full page of recent listings; very small towns return one or two.
- **Run once a day.** The first page shows the newest listings, so a daily run is enough to catch what is new without paying for the same rows repeatedly.
- **Sort on `priceDropPct`.** The biggest reductions are the ones worth a call.
- **Prices are asking prices.** They are what the seller listed, not what the flat sells for.

### vs. alternatives

None of our other actors read Mubawab, so the comparison is with doing it by hand.

| | What it costs | What you get | Trade-off |
| --- | --- | --- | --- |
| **This actor** (`mubawab-property-alerts`) | $0.002 per listing returned, $0.003 per run | A scheduled, structured feed of new and reduced listings for the cities that work | First page only, sale listings only, a city typed with the wrong spelling returns nothing, and a few listings show no price |
| **Browsing Mubawab yourself** | Your time | The full site: every page, rentals, photos, agency contacts, saved searches | Nothing arrives on a schedule or in a spreadsheet, and you cannot compare yesterday's price to today's |

### FAQ

#### Can I monitor rentals?

No. The actor reads the for-sale section only.

#### Are all prices in dirhams?

`MAD` is the default currency, and a different one is used only if the listing's price text shows it. Check the `currency` column before comparing across rows.

#### Does it open each listing page?

No. It reads the results page only, so the fields come from the listing cards.

#### Why do I get fewer rows than the website shows?

Because it reads one page, not the whole result set.

### Related actors

- [Daangn Listing Lookup](https://apify.com/accountable_eel/daangn-listing-lookup) is a different market but the same idea: a keyword search turned into a structured listing feed.
- [PropertyFinder Listing Lookup](https://apify.com/accountable_eel/propertyfinder-listing-lookup) covers Dubai listings from a list of URLs.

# Actor input Schema

## `searches` (type: `array`):

One Moroccan city per line, spelled as in Mubawab addresses. E.g. "Casablanca", "Marrakech", "Agadir". Accepted formats: Casablanca, Marrakech, Agadir. You're only charged for the ones we actually find — a miss costs nothing.

## `testRun` (type: `boolean`):

Turn this on to test your input on a small sample before running the full list. Turn it off to process everything.

## `onlyFound` (type: `boolean`):

Only keep rows where something was actually found. Misses are always free, whether or not you show them here.

## `includeKeywords` (type: `array`):

Optional. Only keep results that mention at least one of these words (e.g. a job title, a city, a product name). Leave empty to keep everything.

## `excludeKeywords` (type: `array`):

Optional. Drop any result that mentions one of these words. Leave empty to skip nothing.

## `maxResults` (type: `integer`):

Optional. Stop the run once this many results have been found — useful for a quick, cheap sample. Leave blank for no limit.

## `city` (type: `string`):

Optional exact city name as it appears in Mubawab addresses (e.g. "Casablanca", "Marrakech", "Agadir", "Tanger"). Replaces the search text when set.

## `propertyType` (type: `string`):

Label written into each row's propertyType column. It does not change which listings are fetched; use Only keep rows containing (e.g. villa) to narrow.

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

Optional minimum price filter in local currency (MAD/TND).

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

Optional maximum price filter in local currency (MAD/TND).

## `deltaMode` (type: `boolean`):

When enabled, only returns listings that are new or dropped in price since last run.

## `deltaName` (type: `string`):

Optional custom state key for KV store memory.

## `alertOnNew` (type: `boolean`):

Emit alerts when new listings appear.

## `alertOnPriceDrop` (type: `boolean`):

Emit alerts when listing prices drop.

## `minPriceDropPct` (type: `integer`):

Minimum price drop percentage to trigger an alert.

## `skipFirstRun` (type: `boolean`):

Silently seed initial state without emitting alerts on first run.

## `columns` (type: `array`):

Choose which pieces of information to include in each result row. All are included by default.

## `expandRows` (type: `boolean`):

When on, each listing found gets its own row instead of being grouped under its search. You're still only charged once per search, no matter how many rows it produces.

## `maxConcurrency` (type: `integer`):

Parallel requests. Keep conservative — this target has no browser fallback, so getting blocked costs more than slow-and-steady.

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

Apify Proxy config. Residential recommended for anti-bot-sensitive targets.

## Actor input object example

```json
{
  "searches": [
    "Casablanca"
  ],
  "testRun": false,
  "onlyFound": false,
  "includeKeywords": [],
  "excludeKeywords": [],
  "city": "",
  "propertyType": "all",
  "deltaMode": false,
  "deltaName": "",
  "alertOnNew": true,
  "alertOnPriceDrop": true,
  "minPriceDropPct": 5,
  "skipFirstRun": false,
  "columns": [
    "searchQuery",
    "listingCount",
    "newCount",
    "priceDropCount",
    "listings",
    "listingId",
    "title",
    "description",
    "price",
    "currency",
    "city",
    "location",
    "propertyType",
    "area",
    "bedrooms",
    "bathrooms",
    "url",
    "imageUrl",
    "isNew",
    "changeType",
    "previousPrice",
    "priceDropPct",
    "firstSeen",
    "lastSeen"
  ],
  "expandRows": true,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (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 = {
    "searches": [
        "Casablanca"
    ],
    "includeKeywords": [],
    "excludeKeywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("accountable_eel/mubawab-property-alerts").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 = {
    "searches": ["Casablanca"],
    "includeKeywords": [],
    "excludeKeywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("accountable_eel/mubawab-property-alerts").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 '{
  "searches": [
    "Casablanca"
  ],
  "includeKeywords": [],
  "excludeKeywords": []
}' |
apify call accountable_eel/mubawab-property-alerts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,accountable_eel/mubawab-property-alerts"
        }
    }
}
```

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/LckUeX68HTqpZEE5o/builds/kSigE3LiXhIxcuC7y/openapi.json
