# Bayt Jobs Scraper: UAE, Saudi, Qatar, GCC (filters, alerts) (`datahamster/bayt-jobs`) Actor

Bayt scraper for gulf jobs: Bayt.com listings for UAE, Saudi Arabia, Qatar, Kuwait, Bahrain, Oman, Egypt and more as flat rows - title, company, city, posted date, salary, full description on demand. Paste your own filtered search URL and get monitor-mode alerts on new jobs.

- **URL**: https://apify.com/datahamster/bayt-jobs.md
- **Developed by:** [Viktor Dubnytskiy](https://apify.com/datahamster) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 result items

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Bayt Jobs Scraper: UAE, Saudi, Qatar, GCC (filters, alerts)

Scrape job listings from **Bayt.com**, the largest job site in the Middle East, across every Gulf and MENA country
it serves. Give it a keyword and a country, or paste a ready-made Bayt search URL with your own filters, and get one
flat row per job: title, company, city, posting date, salary when the employer prints it, and — with `fetchDetails` —
the full job description, employment type, career level, years of experience and employer industry.

### What you get

One row per job: `id` / `jobId`, `url`, `title`, `company`, `city`, `country`, `postedAt`, `validThrough`,
`employmentType`, `careerLevel`, `yearsExperience`, `industry`, `salary`, `description`, `query`, `page`, `rank`,
`source`, `scrapedAt`.

`id` is the numeric Bayt job id, so the same job never appears twice in one dataset, and monitor mode can track it
across runs.

### Example output

The first column is row 1 of the example dataset; the second is a real row from a Saudi Arabia search,
showing a posting that does print a salary:

| Field | Listing row (UAE, `python`) | Listing row (Saudi Arabia, `accountant`) |
|---|---|---|
| `id` / `jobId` | `5479161` | `5481835` |
| `title` | `.NET Integration Engineer` | `Digital Account Manager` |
| `company` | `Emagine Solutions FZE` | `شركة الكمال للمقاولات` |
| `city` / `country` | `Dubai` / `uae` | `As Salamah` / `saudi-arabia` |
| `postedAt` | `2026-08-27` | `2026-09-10` |
| `careerLevel` | `Mid career` | — |
| `salary` | — | `SAR 3,750 - SAR 5,625` |
| `url` | `https://www.bayt.com/en/uae/jobs/net-integration-engineer-5479161/` | `https://www.bayt.com/en/saudi-arabia/jobs/…-5481835/` |

With `fetchDetails: true` the same row also carries:

| Field | Example value |
|---|---|
| `employmentType` | `FULL_TIME` |
| `validThrough` | `2026-10-26T00:00:00Z` |
| `industry` | `Accounting` |
| `yearsExperience` | `3+ years` |
| `description` | the employer's full job description (HTML, as Bayt publishes it) |

### Input

| Field | Meaning | Default |
|---|---|---|
| `country` | `uae`, `saudi-arabia`, `qatar`, `kuwait`, `bahrain`, `oman`, `egypt`, `jordan`, `lebanon`, `morocco`, `pakistan`, `india`, `international` | `uae` |
| `searches` | Keywords, one per line, e.g. `python`, `senior accountant`, `nurse` | `["python"]` |
| `listingUrls` | Ready-made Bayt search URLs, filters and sorting included | empty |
| `maxPagesPerSearch` | Listing pages per keyword, 30 jobs per page | `3` |
| `fetchDetails` | Open each job page for description, employment type, industry, experience | `false` |
| `maxItems` | Stop after this many rows | `100` |
| `mode` | `scrape` or `monitor` (only new/changed since the last run) | `scrape` |
| `monitorKey`, `webhookUrl`, `telegramBotToken`, `telegramChatId` | Monitor-mode state key and alert targets | empty |

#### Using your own filters

Every filter on Bayt lives in the URL. Filter a search on the site (city, date posted, career level, salary,
job type), copy the address bar into `listingUrls`, and the actor walks it page by page. The country of such a URL
is taken from the URL itself, so one run can mix several countries.

### Job alerts (monitor mode)

Set `mode: monitor` and save the run as a task on a schedule. Each run compares against the previous one and returns
**only** new or changed jobs, keyed by job id, with the change decided on `title | company | postedAt`. Point
`webhookUrl` or the Telegram fields at your own channel and every new Gulf job matching your keywords arrives there.
You pay a monitor-check event per run plus a change event per job that actually moved.

### Pricing

| Event | Price |
|---|---|
| result | $0.001 per job ($1 per 1,000) |
| monitor-check | $0.005 per monitor run |
| change | $0.001 per new/changed job |

You are charged only for rows actually pushed. **Residential proxy traffic is billed by Apify on top of these
events**: Bayt answers datacenter IPs with a Cloudflare interstitial, so every request goes through a residential
exit. Budget for it in your Apify plan; a run of a few hundred jobs is a few megabytes of residential traffic.

### Limits

- Bayt serves 30 jobs per listing page, and a search tops out at the pages Bayt itself publishes. Asking for
  a page past the last one redirects back to the last page, so the actor stops there by itself: setting
  `maxPagesPerSearch` higher than a search actually has costs nothing and returns no duplicates.
- `salary` appears only when the employer chose to publish it — most postings do not.
- `description`, `employmentType`, `industry`, `yearsExperience` and `validThrough` need `fetchDetails: true`
  (one extra request per job, so a details run is slower and costs more proxy traffic).
- `description` is the HTML Bayt publishes in its `JobPosting` structured data, tags included.
- English pages only (`/en/`); Arabic job titles are returned exactly as the employer wrote them.
- No applicant, recruiter or contact data — this actor returns job postings and employer names, nothing person-level.

### FAQ

**Which countries are covered?** Every country Bayt has a jobs section for: UAE, Saudi Arabia, Qatar, Kuwait,
Bahrain, Oman, Egypt, Jordan, Lebanon, Morocco, Pakistan, India, plus `international`.

**Can I search several keywords at once?** Yes — `searches` takes a list, and each keyword is walked separately
with its own `query`, `page` and `rank` on every row.

**What happens when a search finds nothing?** No rows are pushed and no result events are charged. The `RUN_SUMMARY`
record in the run's key-value store carries `emptyReason`, which separates Bayt's own "no jobs matching your search
criteria" answer (`no_matches`) from a refused request (`blocked`) or moved markup (`source_changed`).

**Does it need a Bayt login?** No. Only pages Bayt serves to logged-out visitors are read.

### Changelog

- 0.1: initial release — 13 countries, keyword and URL searches, pagination, optional job-page details, monitor mode.

***

If this actor saved you time, a short review on its Store page genuinely helps other people find it. Found a bug or
need a field that is missing? Open a ticket on the **Issues** tab.

# Actor input Schema

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

Stop after this many results (you are charged only for pushed items)

## `mode` (type: `string`):

scrape = full results; monitor = only new/changed items since the previous run of this task

## `monitorKey` (type: `string`):

Optional state key when not running as a saved task

## `webhookUrl` (type: `string`):

POST a change summary here in monitor mode

## `telegramBotToken` (type: `string`):

Optional: bot token for monitor-mode change summaries

## `telegramChatId` (type: `string`):

Optional: chat id that receives monitor-mode summaries

## `country` (type: `string`):

Bayt country section the keywords are searched in, e.g. UAE. Ignored for rows coming from Listing URLs, which carry their own country.

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

Keywords to search jobs for, one per line, e.g. "python", "senior accountant", "nurse", "sales manager". Each keyword becomes a Bayt search in the selected country.

## `listingUrls` (type: `array`):

Ready-made Bayt search URLs with your own filters, e.g. "https://www.bayt.com/en/uae/jobs/python-jobs-in-dubai/". Filter on the site, copy the address bar, paste it here. Can be combined with Search keywords.

## `maxPagesPerSearch` (type: `integer`):

How many listing pages to walk per keyword or URL, 30 jobs per page, e.g. 3 for the newest ~90 jobs. Range 1-50.

## `fetchDetails` (type: `boolean`):

true = open every job page for the full description, employment type, career level, years of experience, industry and expiry date (one extra request per job). false = listing fields only, which is faster and cheaper. Example: false.

## Actor input object example

```json
{
  "maxItems": 100,
  "mode": "scrape",
  "country": "uae",
  "searches": [
    "python"
  ],
  "maxPagesPerSearch": 3,
  "fetchDetails": false
}
```

# Actor output Schema

## `results` (type: `string`):

All pushed rows (dataset, JSON)

## `resultsTable` (type: `string`):

Dataset in the Console viewer

## `runSummary` (type: `string`):

RUN\_SUMMARY record

# 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 = {
    "country": "uae",
    "searches": [
        "python"
    ],
    "maxPagesPerSearch": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("datahamster/bayt-jobs").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 = {
    "country": "uae",
    "searches": ["python"],
    "maxPagesPerSearch": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("datahamster/bayt-jobs").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 '{
  "country": "uae",
  "searches": [
    "python"
  ],
  "maxPagesPerSearch": 3
}' |
apify call datahamster/bayt-jobs --silent --output-dataset

```

## MCP server setup

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

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/sfrcIGuMB08Qdu89H/builds/ccCAYSCLrmKoGaBed/openapi.json
