# Bayt Jobs Scraper (`mina_safwat/bayt-jobs-scraper`) Actor

Scrapes job listings from any Bayt.com search in the UAE, Saudi Arabia, Egypt, Qatar, Kuwait and the other Bayt countries, in English or Arabic, with every filter kept: title, company, location, salary, career level, experience, remote, posted date and full description.

- **URL**: https://apify.com/mina_safwat/bayt-jobs-scraper.md
- **Developed by:** [Mina](https://apify.com/mina_safwat) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 jobs

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

### What does Bayt Jobs Scraper do?

**Bayt Jobs Scraper** collects **job listings from [Bayt.com](https://www.bayt.com)**, the largest job site in the Middle East and North Africa. It works with any Bayt search in the **UAE, Saudi Arabia, Egypt, Qatar, Kuwait, Bahrain, Oman, Jordan, Lebanon, Morocco, Algeria, Tunisia, Iraq, Libya, Pakistan, India** or **International**, on the **English or Arabic** site. Search on Bayt as you normally would, paste the link, and get every job as a clean row: **title, company, company page and logo, city and country, monthly salary range with numbers and currency, career level, years of experience, remote or hybrid, nationals-only flag, posted date, Bayt's short summary**, and on request the **full job description, employment type, number of openings and closing date**.

**Every filter in your link is kept**: keywords, city, career level, date posted, employment type, company, industry, gender, sort order and anything else Bayt puts in the address. Arabic searches come back in Arabic, exactly as Bayt shows them.

Try it with the prefilled search: open the Actor, click **Start**, and 25 UAE software engineer jobs are ready in a few seconds. Running on the Apify platform also gives you API access, scheduled runs, integrations (Google Sheets, Make, Zapier, Slack, webhooks) and run monitoring.

### Why scrape Bayt?

- **Salary benchmarking across the Gulf.** Bayt shows monthly salary bands in local currency (AED, SAR, QAR, EGP and others). The Actor turns them into numbers you can sort and average, with the original text kept alongside.
- **Labour market research.** Track demand by country, city, career level and experience, including jobs reserved for UAE or Saudi nationals.
- **Recruitment and sales leads.** See which companies are hiring in the region, for what, and where.
- **Job aggregation.** Feed a niche job board, newsletter or internal tool with fresh, structured listings in English and Arabic.
- **Monitoring.** Schedule a daily run on a newest-first search and keep only the job IDs you have not seen.

### How to scrape Bayt jobs

1. Open [bayt.com](https://www.bayt.com), pick the country and language, and search: type the job title, choose a city, then set any filters (career level, date posted, employment type, company, industry, remote).
2. Copy the address from your browser's address bar, for example `https://www.bayt.com/en/uae/jobs/software-engineer-jobs/`.
3. Paste it into **Bayt search links**. Add as many links as you like, one per line; countries and languages can be mixed.
4. Set **Max jobs per search link**, and turn on **Include full job descriptions** if you need them.
5. Click **Start**, then download the results as JSON, CSV, Excel or HTML.

### Input

Everything is set on the Input tab. In JSON, the input looks like this:

```json
{
    "search_urls": [
        "https://www.bayt.com/en/uae/jobs/software-engineer-jobs/",
        "bayt.com/ar/saudi-arabia/jobs/accountant-jobs/",
        { "url": "https://www.bayt.com/en/egypt/jobs/sales-jobs/?filters%5Bjb_last_modification_date_interval%5D%5B%5D=3" }
    ],
    "max_results_per_search": 100,
    "include_description": false
}
```

| Field | What it does |
|---|---|
| `search_urls` | Bayt search results links, one per line, on any country and on the English (`/en/`) or Arabic (`/ar/`) site. `https://` is optional. API callers can also send `{"url": "…"}` objects. Single job ads and company pages are not searches; they are listed in `FAILED_INPUTS` with the reason. |
| `max_results_per_search` | Stops after this many jobs from each search link. Default: 100. Bayt shows 30 jobs per page and the Actor reads pages until it has enough or the search runs out. |
| `include_description` | Opens each job's own page to add the full description, employment type, number of openings and closing date. Default: off. It takes one extra request per job, so it is slower and charged a little extra per job (see the Pricing tab). |
| `proxy_configuration` | Leave empty. Bayt's Cloudflare protection turns away most cloud addresses, so the Actor tries a direct connection and Apify's datacenter proxy first and falls back to Apify's residential proxy on its own. Choosing a proxy sends every request through it. |

#### Which filters are kept?

All of them: the Actor keeps the link exactly as you pasted it and only changes the page number. Some examples of what Bayt puts in its links:

| On Bayt | In the link |
|---|---|
| Country and language | `/en/uae/`, `/ar/saudi-arabia/`, `/en/international/` |
| Keywords and city | `/software-engineer-jobs-in-dubai/` |
| Remote jobs | `/remote-jobs/` |
| Date posted | `filters[jb_last_modification_date_interval][]=3` |
| Career level | `filters[jb_career_level_id_original][]=2` |
| Employment type | `filters[jb_employment_type][]=1` |
| Company | `filters[employer_company_name][]=michael page` |
| Sort order | `options[sort][]=d` (newest first) |

The page number in a link is ignored: every search starts from its first page.

### Output

Each job becomes one row. Here is a real one, with the summary and description shortened:

```json
{
    "job_id": "5483975",
    "url": "https://www.bayt.com/en/uae/jobs/technical-product-engineer-backend-full-stack-ai-enabled-5483975/",
    "title": "Technical Product Engineer – Backend / Full Stack (AI-Enabled)",
    "company": "Matajer AI",
    "company_url": "https://www.bayt.com/en/company/matajer-ai-2318804/",
    "company_logo": "https://secure.b8cdn.com/70x70/images/logo/04/2318804_logo_1789704012_n.png",
    "location": "Ajman, UAE",
    "city": "Ajman",
    "country": "UAE",
    "posted_at": "2026-09-19T14:52:24Z",
    "salary": "AED 11,018 - AED 14,690",
    "salary_min": 11018,
    "salary_max": 14690,
    "salary_currency": "AED",
    "career_level": "Mid career",
    "experience": "2-6 Years of Experience",
    "experience_min_years": 2,
    "experience_max_years": 6,
    "work_arrangement": null,
    "citizenship": null,
    "is_featured": false,
    "easy_apply": true,
    "summary": "A high-impact opportunity exists for a hands-on Technical Product Engineer specializing in Backend and Full Stack development...",
    "description": "💼 Job Description\n\nTechnical Product Engineer – Backend / Full Stack (AI-Enabled)\n\nMatajer AI is looking for a hands-on Technical Product Engineer...",
    "employment_type": "FULL_TIME",
    "openings": 2,
    "valid_through": "2026-10-19T00:00:00Z",
    "search_position": 4,
    "search_url": "https://www.bayt.com/en/uae/jobs/software-engineer-jobs/",
    "scraped_at": "2026-10-10T14:05:02Z"
}
```

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel. The Output tab has two views: **Jobs** (the main columns) and **Full details**.

### Data fields

| Field | Description |
|---|---|
| `job_id` | Bayt's job number. |
| `url` | Link to the job ad, on the same site language as your search. |
| `title` | Job title, as shown (Arabic on the Arabic site). |
| `company` | Company name. "Confidential Company" when the employer hides it. |
| `company_url` | The company's Bayt page. Empty for confidential employers and for jobs Bayt collected from other sites. |
| `company_logo` | The company logo. Empty when Bayt shows a placeholder. |
| `location` | The location exactly as Bayt shows it, for example "Al Olaya, Riyadh, Saudi Arabia". |
| `city`, `country` | The city (empty when the job names only a country) and the country, in the site's language. |
| `posted_at` | When the job was posted or last refreshed on Bayt, in UTC. |
| `salary` | The monthly salary band as Bayt shows it, converted by Bayt into the country's currency. Empty when the employer hides it. |
| `salary_min`, `salary_max` | The band as numbers (per month). |
| `salary_currency` | `AED`, `SAR`, `EGP`, `QAR`, … or `USD` for "$". |
| `career_level` | Such as "Entry level", "Mid career", "Management", when the employer gave it. |
| `experience` | Years of experience as shown, such as "2-6 Years of Experience". |
| `experience_min_years`, `experience_max_years` | Those years as numbers, smallest first, even where an Arabic card writes the range highest first ("4-0 سنوات من الخبرة" gives 0 and 4). |
| `work_arrangement` | "Remote" or "Hybrid" (or the Arabic words) when Bayt labels the job so; empty otherwise. |
| `citizenship` | Who may apply when the job is reserved, such as "UAE nationals" or "Saudi nationals". |
| `is_featured` | `true` for jobs Bayt shows as featured. |
| `easy_apply` | `true` when the job can be applied for with Bayt's Easy Apply. |
| `summary` | Bayt's short summary of the job shown in search results. |
| `description` | The full job description as plain text (only with **Include full job descriptions**). |
| `employment_type` | `FULL_TIME`, `PART_TIME`, `CONTRACTOR`, … as Bayt states it on the job page (only with descriptions). |
| `openings` | Number of open positions, when the employer gave one (only with descriptions). |
| `valid_through` | When the ad closes, in UTC (only with descriptions). |
| `search_position` | The job's place in your search, starting at 1. |
| `search_url` | The search link that found this job. |
| `scraped_at` | When the job was collected, in UTC. |

The four description fields are left out of the rows entirely when descriptions are off. A job whose page could not be read, or has no description, is still saved with an empty description and is not charged the description price; the run's status message says how many.

### How much does it cost to scrape Bayt?

The Actor uses **pay-per-event** pricing: you pay a small fixed price for each job saved, and a small extra price for each job saved with its full description. The exact prices are on the **Pricing** tab.

Descriptions cost extra because Bayt shows 30 jobs on a results page but one job per job page: a run with descriptions makes about 30 times as many requests, all through residential proxies.

You are never charged for links that return nothing: a search with no jobs, a page that does not exist or a link that is not a Bayt search is listed in the run's `FAILED_INPUTS` record instead. If you set a maximum cost for a run, the Actor stops as soon as it is reached.

### Tips for scraping Bayt

- **Let Bayt filter for you.** Narrow the search on Bayt before copying the link. Bayt applies the filters itself, so you only collect (and pay for) matching jobs.
- **Monitor new jobs.** Sort by date (`options[sort][]=d`) and choose a recent "date posted" filter, schedule the Actor daily, and drop job IDs you already have.
- **Skip descriptions when you can.** Titles, companies, salary, level, experience, location and Bayt's summary all come from the results pages; leave descriptions off for runs that are faster and cheaper.
- **Arabic and English.** The same search on `/ar/` returns the same jobs with Arabic titles, places and labels; descriptions are Bayt's own Arabic text.
- **Each job is saved once per run**, even when several of your links find it.

### FAQ, disclaimers and support

**Which Bayt links work?** Search results pages on any Bayt country and on both the English and Arabic sites. Single job ads, company pages and Bayt's "Work from Home Jobs" directory are not lists of jobs; they are listed in `FAILED_INPUTS` with the reason, and so is any other page that turns out not to be a list of jobs. For remote jobs, use a search link such as `https://www.bayt.com/en/uae/jobs/remote-jobs/`.

**Why do many jobs have no salary or career level?** Employers choose what to show. The Actor fills these fields only when Bayt shows them and never estimates them.

**What are jobs without a company page?** Some listings are collected by Bayt from employers' own career sites. They have a company name but no Bayt company page, and their ads link back to the employer.

**What happens to links that return nothing?** They are not saved as rows, so you are not charged for them. Each is listed with its reason in the `FAILED_INPUTS` record of the run's key-value store, and the run's status message gives the counts. A run fails only when Bayt could not be reached for any of your links.

**Is it legal to scrape Bayt?** This Actor collects job listings that employers publish publicly so that people can find and apply for them. It does not log in and collects no personal data about job seekers or recruiters. Job ads can still contain contact details written by the employer, and their text belongs to the employer. You are responsible for making sure your use complies with Bayt's terms and with the laws that apply to you, such as privacy and copyright rules for republishing ads. If in doubt, ask a lawyer.

**Found a problem or need something custom?** Open an issue on the **Issues** tab and we will look into it. Custom scrapers and job data feeds are available on request.

# Actor input Schema

## `search_urls` (type: `array`):

One link per line. Search on bayt.com (English or Arabic site, any country: UAE, Saudi Arabia, Egypt, Qatar, Kuwait, Bahrain, Oman, Jordan, Lebanon, Morocco and the others, or International), set any filters you like, then copy the address bar. Keywords, city, career level, experience, date posted, remote, company, industry, gender, sort order and every other filter in the link are kept. Links without https:// are fine. Single job ads and company pages are not searches and are listed in the FAILED_INPUTS record.

## `max_results_per_search` (type: `integer`):

Stops after this many jobs from each search link. Bayt shows 30 jobs per page; the Actor reads pages until it has this many or the search runs out.

## `include_description` (type: `boolean`):

Opens each job's own page to add its full description as plain text, plus its employment type (FULL_TIME, PART_TIME, ...), number of openings and closing date. This takes one extra request per job (search results bring 30 jobs per request), so it is slower and each job read this way is charged a little extra; see the Pricing tab. Off, every row still has Bayt's short summary of the job.

## `proxy_configuration` (type: `object`):

Leave this empty. Bayt's Cloudflare protection turns away most cloud addresses, so the Actor tries a direct connection and Apify's datacenter proxy first and falls back to Apify's residential proxy on its own. Choose a proxy here only to send every request through it.

## Actor input object example

```json
{
  "search_urls": [
    "https://www.bayt.com/en/uae/jobs/software-engineer-jobs/"
  ],
  "max_results_per_search": 25,
  "include_description": false,
  "proxy_configuration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (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 = {
    "search_urls": [
        "https://www.bayt.com/en/uae/jobs/software-engineer-jobs/"
    ],
    "max_results_per_search": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("mina_safwat/bayt-jobs-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 = {
    "search_urls": ["https://www.bayt.com/en/uae/jobs/software-engineer-jobs/"],
    "max_results_per_search": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("mina_safwat/bayt-jobs-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 '{
  "search_urls": [
    "https://www.bayt.com/en/uae/jobs/software-engineer-jobs/"
  ],
  "max_results_per_search": 25
}' |
apify call mina_safwat/bayt-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mina_safwat/bayt-jobs-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/AhxvXDK1fhcw9dRgv/builds/coJl7BzjuwhuchzRP/openapi.json
