# Bayut KSA Property Scraper (`datascrapers/bayut-sa-scraper`) Actor

Extract property listings from Bayut.sa with optional full listing details and agent information.

- **URL**: https://apify.com/datascrapers/bayut-sa-scraper.md
- **Developed by:** [Data Scrapers](https://apify.com/datascrapers) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.20 / 1,000 property listings

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

**Bayut KSA Property Scraper** creates a structured dataset of property listings collected from Bayut Saudi Arabia. Each dataset item is one listing and can include the listing id, purpose, price in SAR, rooms, baths, area, location path, cover image, and public URL. Query by purpose, city or neighbourhood, category, and property type, optionally add the listing page and the agent profile, limit results with `maxItems`, and retrieve records through the Apify Dataset API or export them as JSON, CSV, Excel, XML, or another supported format.

[![Bayut KSA Property Scraper — input to structured data](https://raw.githubusercontent.com/faizanalii/apify-actor-assets/main/bayut-sa-scraper/bayut-sa-scraper-value-demo.gif)](https://apify.com/datascrapers/bayut-sa-scraper)

### Dataset at a glance

| Property | Value |
|---|---|
| Source | bayut.sa |
| Record unit | One property listing |
| Input methods | `purpose`, `location`, `category`, `subCategory`, and the other filters |
| Main identifiers | `externalID` and `id` |
| Delivery | Apify Dataset and API |
| Export formats | JSON, CSV, Excel, XML, and other Apify dataset exports |
| Update model | Fresh records per Actor run |
| Pricing | $2.50 per 1,000 listings, $2.00 per 1,000 detail fetches, $1.50 per 1,000 agent profiles on the free plan |

### Coverage and available records

The Actor searches Bayut Saudi Arabia. `purpose` is `for-sale` or `for-rent`. `location` is a city, neighbourhood, or path. Set `parentLocation` when `location` is a neighbourhood and needs a parent city. Category is `residential` or `commercial`. `subCategory` is a property-type slug such as `apartments` or `villas`.

Other filters: completion status (`all`, `ready`, `off-plan`), rent frequency, furnishing, bedroom and bathroom bounds, area in square feet, and price in SAR. `superHot` limits the search to superhot for-sale listings.

`maxPages` defaults to 1. An empty value means all pages. `maxItems` defaults to 0, which means no item cap. The prefill is 100.

The search card includes title, Arabic title, price, rooms, baths, area, geography, location hierarchy, category, cover image, photo and video counts, agency, verification, and the public `propertyUrl`. The Actor also writes `priceFormatted`, `propertyType`, `fullAddress`, `coverImageUrl`, `agencyName`, and `agencyUrl`.

`scrapePropertyDetails` adds `propertyDetails` and `amenities`. `scrapeAgentInformation` adds `agentInformation`. Both are off by default. The saved Riyadh sample had both on. Phone numbers and an agent email were present on that listing. They are not copied into the example below.

Not currently collected: a sold-price history, or listings outside the filters you set.

### Data dictionary

The table uses a saved for-sale apartment in Al Rimal, Riyadh. Area on that card is 86. Price is 550,000 SAR.

| Field | Type | Nullable | Description | Example |
|---|---|---:|---|---|
| `id` | integer | No | Internal listing id. Also stored as `objectID`. | `1358011` |
| `externalID` | string | No | Public listing number. Best field for deduplication. | `88006942` |
| `propertyUrl` | string | No | English listing URL built by the Actor. | `https://www.bayut.sa/en/property/details-88006942.html` |
| `purpose` | string | No | `for-sale` or `for-rent`. | `for-sale` |
| `price` | number | Yes | Price in SAR. | `550000.0` |
| `priceFormatted` | string | Yes | Display price. | `SAR 550,000` |
| `rentFrequency` | string or null | Yes | Rent period. Null on the saved sale. | `null` |
| `title` | string | Yes | English title. | Apartment for sale in Al Rimal |
| `title_l1` | string | Yes | Arabic title. | Arabic title string |
| `rooms` | integer or null | Yes | Bedrooms. | `2` |
| `baths` | integer or null | Yes | Bathrooms. | `2` |
| `area` | number or null | Yes | Area. The saved card is 86. The input filter unit is square feet. | `86.0` |
| `propertyType` | string | Yes | Type label added by the Actor. | `Apartment` |
| `fullAddress` | string | Yes | City, area, and district joined by the Actor. | `Riyadh, East Riyadh, Al Rimal` |
| `geography` | object | Yes | `lat` and `lng`. | `24.90263, 46.83518` |
| `location` | array | No | Hierarchy from country to district, each with `id`, `name`, `name_l1`, and `slug`. | KSA, Riyadh, East Riyadh, Al Rimal |
| `category` | array | No | Category path, residential then apartment on this card. | `Residential` |
| `furnishingStatus` | string or null | Yes | Furnishing label. | `unfurnished` |
| `completionStatus` | string or null | Yes | Completion label. | `completed` |
| `state` | string | Yes | Listing state. | `active` |
| `coverImageUrl` | string or null | Yes | Cover image URL. | `https://images.bayut.sa/thumbnails/...` |
| `photoCount` | integer | Yes | Photo count. | `14` |
| `videoCount` | integer | Yes | Video count. | `1` |
| `hasVideo` | boolean | Yes | True when a video is present. | `true` |
| `agencyName` | string or null | Yes | Agency name. | Agency name string |
| `agencyUrl` | string or null | Yes | Agency page URL. | `https://www.bayut.sa/en/companies/...` |
| `isVerified` | boolean | Yes | Verification flag from the card. | `true` |
| `hidePrice` | boolean | Yes | True when the source hides the price. | `false` |
| `permitNumber` | string or null | Yes | Permit number when present. Null on the saved card. | `null` |
| `referenceNumber` | string or null | Yes | Reference number. Null on the saved card. | `null` |
| `createdAt` | number | Yes | Source timestamp as a Unix number. | `1790760790.0` |
| `updatedAt` | number | Yes | Source update timestamp. | `1790760790.0` |

`propertyDetails` is the listing-page object when `scrapePropertyDetails` is true. `amenities` is then an array of amenity names. `agentInformation` is the agent profile when `scrapeAgentInformation` is true. `phoneNumber` can contain mobile, phone, and WhatsApp values. `agentEmail` is a string when the listing exposes one. Those contact fields are omitted from the example.

### Example dataset record

Saved from purpose `for-sale`, location `Riyadh`, category `residential`, subCategory `apartments`, with details and agent information enabled. Contact names, phone numbers, and email are on the stored record and are not repeated here. `location` keeps the district level only.

```json
{
  "id": 1358011,
  "externalID": "88006942",
  "objectID": "1358011",
  "purpose": "for-sale",
  "price": 550000.0,
  "priceFormatted": "SAR 550,000",
  "rentFrequency": null,
  "title": "**Apartment for Sale | Al Sadaf 27 Project | Al Rimal District**",
  "rooms": 2,
  "baths": 2,
  "area": 86.0,
  "propertyType": "Apartment",
  "fullAddress": "Riyadh, East Riyadh, Al Rimal",
  "geography": { "lat": 24.902634636864, "lng": 46.835184996003 },
  "furnishingStatus": "unfurnished",
  "completionStatus": "completed",
  "state": "active",
  "propertyUrl": "https://www.bayut.sa/en/property/details-88006942.html",
  "coverImageUrl": "https://images.bayut.sa/thumbnails/9551980-800x600.webp",
  "photoCount": 14,
  "videoCount": 1,
  "hasVideo": true,
  "isVerified": true,
  "hidePrice": false,
  "permitNumber": null,
  "referenceNumber": null
}
```

### Query and input reference

| Input | Type | Required | Default | Accepted values | Description |
|---|---|---:|---|---|---|
| `purpose` | string | No | `for-sale` | `for-sale`, `for-rent` | Listing purpose. |
| `location` | string | No | `Riyadh` | City, neighbourhood, or path | Search place. |
| `parentLocation` | string | No | empty | Parent city dropdown, or empty | Required in the UI sense only for sub-areas. |
| `category` | string | No | `residential` | `residential`, `commercial` | Category. |
| `subCategory` | string | No | `apartments` | Property-type slugs in the input schema | Example: `apartments`, `villas`, `offices`. |
| `status` | string | No | `all` | `all`, `ready`, `off-plan` | Completion filter. |
| `rentFrequency` | string | No | None | `yearly`, `monthly`, `weekly`, `daily` | Rent listings only. |
| `furnishingStatus` | string | No | None | `all`, `furnished`, `unfurnished` | Furnishing filter. |
| `roomsMin` | integer | No | None | Integer | Minimum bedrooms. |
| `roomsMax` | integer | No | None | Integer | Maximum bedrooms. |
| `bathsMin` | integer | No | None | Integer | Minimum bathrooms. |
| `bathsMax` | integer | No | None | Integer | Maximum bathrooms. |
| `areaMin` | integer | No | None | Square feet | Minimum area. |
| `areaMax` | integer | No | None | Square feet | Maximum area. |
| `priceMin` | integer | No | None | SAR | Minimum price. |
| `priceMax` | integer | No | None | SAR | Maximum price. |
| `superHot` | boolean | No | `false` | `true` or `false` | Superhot for-sale listings only. |
| `scrapePropertyDetails` | boolean | No | `false` | `true` or `false` | Charge `property-details`. |
| `scrapeAgentInformation` | boolean | No | `false` | `true` or `false` | Charge `agent-information`. |
| `detailsConcurrency` | integer | No | `5` | Integer | Parallel detail and agent requests. |
| `maxPages` | integer | No | `1` | Positive integer, or empty for all pages | Page cap. |
| `maxItems` | integer | No | `0` | `0` or a positive integer | Listing cap. `0` means no cap. Prefill `100`. |
| `proxyConfiguration` | object | No | Apify residential proxy, country `SA` | Apify proxy editor | Proxy for search, detail, and agent requests. |

Minimal request:

```json
{
  "purpose": "for-sale",
  "location": "Riyadh",
  "category": "residential",
  "subCategory": "apartments",
  "maxPages": 1,
  "maxItems": 5
}
```

With details and agent profiles:

```json
{
  "purpose": "for-sale",
  "location": "Riyadh",
  "subCategory": "apartments",
  "maxItems": 5,
  "scrapePropertyDetails": true,
  "scrapeAgentInformation": true
}
```

### Retrieve the data through the API

1. Start `datascrapers/bayut-sa-scraper` with the JSON input.
2. Wait until the run finishes, or call the synchronous dataset endpoint.
3. Read items from the run's default dataset.
4. Paginate with dataset `offset` and `limit`, or export from the Apify console.

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("datascrapers/bayut-sa-scraper").call(
    run_input={
        "purpose": "for-sale",
        "location": "Riyadh",
        "category": "residential",
        "subCategory": "apartments",
        "maxItems": 5,
    }
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["externalID"], item["fullAddress"], item["price"])
```

Other languages are listed on the Actor's API tab. This is the Apify Actor API, not a Bayut API.

### Data quality and record handling

- One dataset item is one listing. The `property` event is charged for that listing.
- Deduplicate on `externalID`. `id` and `objectID` are the same internal number on the saved card.
- `propertyUrl` is built from `externalID`. `fullAddress` is built from the location names.
- `createdAt` and `updatedAt` are Unix timestamps from the source, not ISO strings.
- `area` on the saved card is 86. The input area filters are documented as square feet. Do not assume the card unit without checking the listing.
- `propertyDetails` and `amenities` are absent unless detail scraping succeeds. `agentInformation` is absent unless the agent request succeeds.
- A failed detail or agent request does not remove the search card.
- Phone numbers and email are present only when the source listing includes them.

### Pricing and cost examples

The price file charges three events. Amounts are per 1,000 events. The free-plan rates are **$2.50** for `property`, **$2.00** for `property-details`, and **$1.50** for `agent-information`.

| Event | Free | Bronze | Silver | Gold | Platinum and Diamond |
|---|---:|---:|---:|---:|---:|
| `property` | $2.50 | $2.40 | $2.30 | $2.20 | $2.20 |
| `property-details` | $2.00 | $1.90 | $1.80 | $1.70 | $1.70 |
| `agent-information` | $1.50 | $1.40 | $1.30 | $1.20 | $1.20 |

Platinum and Diamond are not listed in the file. They use the Gold rate. The file sets platform usage as not charged to the user. Figures below use the free-plan rates and assume every selected listing is enriched.

| Listings | Cards only | Cards and details | Cards, details, and agent profiles |
|---:|---:|---:|---:|
| 1,000 | $2.50 | $4.50 | $6.00 |
| 10,000 | $25.00 | $45.00 | $60.00 |

### Limitations and responsible data use

- Results follow the public Bayut search you configure. `maxPages` defaults to 1.
- Neighbourhood searches need `parentLocation` when the location is not a city.
- Prices, photos, and verification flags change on the source. This is not a historical archive.
- Phone numbers, email, and agent names are contact data. Use them in line with the privacy rules that apply to your project.
- You are responsible for complying with Bayut's terms and applicable law.

### Dataset questions

#### What does one dataset item represent?

One Bayut Saudi Arabia listing. Detail and agent fields are added only when those options succeed.

#### Which field should I use as a unique identifier?

Use `externalID`. It is the number in `propertyUrl`.

#### Are fields nullable or conditional?

Yes. Rent frequency, permit number, and reference number were null on the saved sale. `propertyDetails`, `amenities`, and `agentInformation` depend on the two scrape options.

#### Can I retrieve the records as CSV or JSON?

Yes. Export the default dataset as JSON, CSV, Excel, XML, or another Apify format.

#### How do I paginate large datasets?

Set `maxItems` and `maxPages`. Read the dataset with `limit` and `offset`. `maxItems` of 0 removes the item cap.

#### Does the Actor return historical data?

No. `createdAt` is the source timestamp on the current listing, not a history of price changes.

#### What counts as a billable result?

Each listing charges `property` at $0.0025 on the free plan. Each successful detail fetch charges `property-details` at $0.002. Each successful agent profile charges `agent-information` at $0.0015.

### Related datasets from Data Scrapers

- [Aqar.fm Scraper](https://apify.com/datascrapers/aqar-fm-scraper) collects Saudi listing cards you can compare on city, price, and area.
- [Apartments Scraper](https://apify.com/datascrapers/apartments-scraper) collects apartment listings with price and location fields.
- [99acres Scraper](https://apify.com/datascrapers/99acres-scraper) is another national listings dataset with price and area.
- [Aengevelt Scraper](https://apify.com/datascrapers/aengevelt-scraper) collects commercial property records.
- [Asunnot Oikotie.fi](https://apify.com/datascrapers/asunnot-oikotie-fi) collects Finnish listing cards with price, size, and address.

### Data Scrapers support

Need an additional field, record type, or export workflow? Contact Data Scrapers at stardustspotlight@gmail.com. Include a sample source URL, required fields, expected record volume, and preferred delivery format.

# Actor input Schema

## `purpose` (type: `string`):

Property listing purpose. Example: for-sale.

## `location` (type: `string`):

City, neighbourhood, or hierarchical path, e.g. Riyadh, Al Narjis (with parentLocation), or riyadh/north-riyadh/al-narjis.

## `parentLocation` (type: `string`):

Only for sub-areas. Select the parent city when location is a neighbourhood (e.g. Riyadh for Al Narjis). Leave as None for top-level cities or full paths.

## `category` (type: `string`):

Property category. Example: residential.

## `subCategory` (type: `string`):

Specific property type slug. Example: apartments.

## `status` (type: `string`):

Property completion status. Example: all.

## `rentFrequency` (type: `string`):

Rent payment frequency (for rent listings only). Example: yearly.

## `furnishingStatus` (type: `string`):

Property furnishing status. Example: furnished.

## `roomsMin` (type: `integer`):

Minimum number of bedrooms. Example: 2.

## `roomsMax` (type: `integer`):

Maximum number of bedrooms. Example: 4.

## `bathsMin` (type: `integer`):

Minimum number of bathrooms. Example: 1.

## `bathsMax` (type: `integer`):

Maximum number of bathrooms. Example: 3.

## `areaMin` (type: `integer`):

Minimum property area in square feet. Example: 800.

## `areaMax` (type: `integer`):

Maximum property area in square feet. Example: 2500.

## `priceMin` (type: `integer`):

Minimum property price in Saudi Riyal. Example: 500000.

## `priceMax` (type: `integer`):

Maximum property price in Saudi Riyal. Example: 2000000.

## `superHot` (type: `boolean`):

Restrict results to superhot listings (for-sale with all completion statuses).

## `scrapePropertyDetails` (type: `boolean`):

When enabled, fetch the full listing detail payload for each property (charged as a separate property-details event). Disabled by default.

## `scrapeAgentInformation` (type: `boolean`):

When enabled, fetch the agent profile for each property (charged as a separate agent-information event). Disabled by default.

## `detailsConcurrency` (type: `integer`):

How many property-detail and agent requests to run in parallel. Example: 5.

## `maxPages` (type: `integer`):

Maximum number of search pages to scrape. Example: 1. Leave empty for all pages.

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

Maximum total listings to collect (0 = unlimited). Example: 50.

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

Apify Proxy groups used for search and rotated per property-detail / agent request.

## Actor input object example

```json
{
  "purpose": "for-sale",
  "location": "Riyadh",
  "parentLocation": "",
  "category": "residential",
  "subCategory": "apartments",
  "status": "all",
  "superHot": false,
  "scrapePropertyDetails": false,
  "scrapeAgentInformation": false,
  "detailsConcurrency": 5,
  "maxPages": 1,
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "SA"
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

Link to the default dataset containing all scraped listings

## `runStats` (type: `string`):

Run statistics stored in the key-value store

# 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 = {
    "location": "Riyadh",
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("datascrapers/bayut-sa-scraper").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 = {
    "location": "Riyadh",
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("datascrapers/bayut-sa-scraper").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 '{
  "location": "Riyadh",
  "maxItems": 100
}' |
apify call datascrapers/bayut-sa-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datascrapers/bayut-sa-scraper"
        }
    }
}
```

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/ltC6J899Mcf7bXI9B/builds/Yx4MqG9X5giy3MbfC/openapi.json
