# Bayt Jobs Scraper — Gulf listings with nationality requirements (`entrogix_works/bayt-gulf-jobs-scraper`) Actor

Scrape Bayt job listings across the Gulf and wider MENA with the two things a raw listing hides: whether the role is restricted to nationals (Emiratisation / Saudization), and how stale the posting is in days rather than "19 days ago". Unofficial; not affiliated with Bayt.

- **URL**: https://apify.com/entrogix\_works/bayt-gulf-jobs-scraper.md
- **Developed by:** [Entrogix Works](https://apify.com/entrogix_works) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 66.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 job 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?

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 — Gulf listings with nationality requirements

Scrape Bayt job listings across the Gulf and wider MENA, with the two things a raw
listing hides: **whether the role is restricted to nationals**, and **how old the posting
actually is** in days rather than "19 days ago".

> Unofficial. Not affiliated with, endorsed by, or connected to Bayt.

### Why this one

Gulf hiring runs on nationalisation policy. Emiratisation and Saudization quotas mean a
meaningful share of postings are open only to nationals — and if you're an expat searching,
or a recruiter tracking quota-driven demand, that single fact decides whether the listing
matters at all. It's written in prose inside the posting, not as a field you can filter on.

| Field | What it answers |
|---|---|
| `nationalityRestrictionMentioned` | Is this nationals-only? |
| `nationalityRestrictionPhrase` | Which words said so — audit it yourself |
| `postedDaysAgo` | Sortable, filterable age |
| `isStale` | Older than 30 days |
| `country` / `city` | Split from the location line, for cross-market comparison |

#### The flag is a floor, and it says so

Detection reads the **listing page only** — the title and the summary Bayt shows there,
not the full posting. So `true` is reliable, `false` is not proof the role is open.
That's why the field is named `…Mentioned` rather than `isRestricted`, and why the
matched phrase ships alongside it. A confident-sounding field that quietly misses cases
is worse than an honest one.

Measured on a live page: **3 of 30** listings carried an explicit requirement.

#### What is deliberately not here

**Salary.** Bayt listings almost never publish it — measured at **1 in 30**. Building a
salary feature on that would mean advertising a column that is empty 97% of the time.

**Contact details.** No recruiter emails, names, or phone numbers, and no enrichment that
adds them. Job listings are company data; the moment you attach people to them it becomes
something else.

### What you get

**Per job:** title, company, city, country, the location line as shown, the nationality
flag and its matched phrase, days since posting, the stale flag, the listing snippet, and URL.

**A `GULF_SUMMARY` record** per run: total jobs, how many mentioned a nationality
requirement and at what rate, median days since posting, how many are over 30 days old,
what share of dates parsed cleanly, the top hiring companies, and the country breakdown.

### Coverage

`uae`, `saudi-arabia`, `qatar`, `kuwait`, `bahrain`, `oman`, `egypt`, `jordan`,
`lebanon`, `morocco`. Run once per country to compare markets — the summary is per run,
so keeping countries separate keeps the medians meaningful.

### Reliability

Success rate was measured **before** this Actor was written: **10/10** through the same
browser path used in production, across two countries. It is monitored twice daily
against a live page.

Proxy settings were measured too, not assumed: **no proxy 0/6, datacenter 6/6**. The
default is datacenter, which costs a fraction of residential. Escalate only if you see blocks.

### Input

```json
{
  "queries": ["data analyst", "software engineer"],
  "country": "uae",
  "maxItemsPerQuery": 60
}
```

Plain search terms become Bayt list URLs for the chosen country. You can also pass full
list URLs if you've already filtered on the site, in which case `country` is ignored.

### Billing

Pay per event: one `job-scraped` event per job actually delivered to your dataset.
Duplicates are removed before charging, and a run that returns nothing costs nothing.

# Actor input Schema

## `queries` (type: `array`):

Job titles to search, or full Bayt list URLs. Plain terms are turned into Bayt list pages for the selected country.

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

Ignored when you pass full URLs. Run the Actor once per country to compare markets.

## `maxItemsPerQuery` (type: `integer`):

Capped at 300. One page returns 30.

## `minIntervalMs` (type: `integer`):

Requests are serialized with at least this gap.

## `useProxy` (type: `boolean`):

Required — Bayt was measured at 0/6 without a proxy.

## `proxyType` (type: `string`):

DATACENTER is measured to work here (6/6) and costs far less than RESIDENTIAL. Only escalate if you hit blocks.

## Actor input object example

```json
{
  "queries": [
    "data analyst",
    "software engineer"
  ],
  "country": "uae",
  "maxItemsPerQuery": 60,
  "minIntervalMs": 3000,
  "useProxy": true,
  "proxyType": "DATACENTER"
}
```

# Actor output Schema

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

Every row this run produced, as JSON.

## `summary` (type: `string`):

Totals and medians for this run, so you can judge the rows without reading them all.

# 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 = {
    "queries": [
        "data analyst",
        "software engineer"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("entrogix_works/bayt-gulf-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 = { "queries": [
        "data analyst",
        "software engineer",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("entrogix_works/bayt-gulf-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 '{
  "queries": [
    "data analyst",
    "software engineer"
  ]
}' |
apify call entrogix_works/bayt-gulf-jobs-scraper --silent --output-dataset

```

## MCP server setup

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