# Bayt Jobs Scraper | GCC & Middle East Job Listings (`s-r/bayt-jobs-scraper`) Actor

Collect Bayt job listings by country, keyword or search URL across the GCC and Middle East. Get employer, location, posting date and summary, with optional full descriptions, job categories and original salary amounts. Built for recruiters and labour market analysis.

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

## Pricing

$1.00 / 1,000 job records

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

## Bayt Jobs Scraper

Bayt Jobs Scraper collects job listings from Bayt.com for recruitment research and labour market analysis in the GCC and the wider Middle East. Search by country and keyword, or paste a Bayt search URL to keep the filters you selected on the website. Each record includes the job title, employer, location, publication date and search card summary. Optional details add the full description, employment type, job category and salary amounts where the source publishes them.

### What Bayt Jobs Scraper collects

The actor covers Bayt country searches across the Gulf, Middle East and North Africa, with additional country options for India and Pakistan. You can search the United Arab Emirates, Saudi Arabia, Qatar, Kuwait, Bahrain, Oman, Egypt, Jordan, Lebanon, Morocco, Algeria, Tunisia, Libya, Iraq, Pakistan and India. The international option searches across the broader catalogue.

Use the results to review open positions, find employers advertising specific roles, or compare demand between locations. Stable Bayt job identifiers help you join exports from repeated searches. The actor removes repeated identifiers within a run, including duplicates appearing on consecutive search pages. It does not infer whether records with different identifiers represent the same vacancy.

### Input

Start with a country and an optional keyword. The default country is `ae`, for the United Arab Emirates. Country codes and Bayt country slugs are accepted without case sensitivity. An empty keyword searches all published jobs in that country. Arabic keywords are supported. Add `city` if you want a city search, such as Dubai or Riyadh.

Alternatively, supply `search_url` with a Bayt country search URL. That URL overrides country, keyword and city. Its filters are retained, and a page parameter sets the starting page. Arabic search page paths are read in English so that the output labels remain consistent. Supply a search page rather than an individual vacancy or employer page.

`sort_by` accepts `relevance` or `date`. If you leave it out, the actor preserves sorting from a pasted URL and otherwise uses Bayt's normal relevance order. `posted_within` accepts `24h`, `7d` or `30d` for a recent posting filter. Other website filters can be included through the search URL.

`max_results` sets the maximum number of unique jobs returned over the whole run. It defaults to 100 and accepts values from 1 to 5000. A limit is a ceiling: a search with fewer available jobs finishes with fewer records. Set `include_details` to true to fetch descriptions and additional metadata. Its default is false.

For compatible input, `query` is an alias for keyword, `startUrl` is an alias for search_url, and `maxResults` or `results_wanted` can replace max_results. `includeDetails` can replace include_details. The first entry of `startUrls` can also provide the search URL, as a string or an object with a url field. Remaining entries are ignored. A canonical input field takes precedence when both versions are present.

### Output

Results are stored in the default dataset, with one row per unique Bayt job identifier. You can download the dataset using Apify's export options. The run output record contains counts, completion status and warnings.

Every job row carries `job_id`, `title`, `url`, `company`, `location`, `country`, `posted_at`, `posted_relative` and `summary`. It also identifies the source platform and provides `fetched_at`, the retrieval time. Employer profile links, logos, salary text, career level, experience and work mode are included when available. Confidential listings keep the employer label published by Bayt, such as Confidential Company.

A country-only location has a null city. `country_code` uses uppercase country codes for the supported country labels and remains null when the card uses another country slug. Missing values are null; the actor does not guess a city, employment type or salary from the title.

With details enabled, rows can include `description`, `employment_type`, `job_category`, `valid_through`, experience in months, candidate nationality and residence values, and direct application metadata. `detail_status` shows whether the detail was collected. If a detail page is unavailable, the listing record remains available, with failed detail status and a run warning.

### Salary and publication dates

The card's `salary_text` is preserved as displayed. Bayt can convert that displayed salary into the page currency, and the card may omit the pay period. Structured `salary_min`, `salary_max`, `salary_currency` and `salary_period` therefore come only from the job detail's published salary information.

For example, the captured Bayt listing for job 5487344 displays AED 29,380 to AED 33,053 on the search card, while its detailed salary information states USD 8000 to USD 9000 per month. Keeping those fields separate prevents an analysis from confusing the display currency with the original salary currency.

Publication timestamps come from the search card. Older aggregated listings may have day-level source precision even though the output uses a UTC timestamp. A detail's date-only posting value does not replace the more precise card timestamp. Relative date labels are preserved separately and should be interpreted against the retrieval time.

### Pagination and completion

The actor follows search pages automatically until it reaches your requested limit or the end of the results. Large searches are processed in several continuation requests. You do not need to supply continuation tokens for normal Apify runs.

Bayt sometimes abbreviates its result count, such as 4K or 7.3K. The actor preserves that display text and only sets the numeric total when the source supplies an exact count. Duplicate records can make the number delivered differ from the number advertised by the search page. Check the actual dataset count when planning downstream work.

`partial` and `warnings` describe unfinished collection or failed optional details. A completed run can still carry a warning about unavailable detail pages. Card fields remain useful in that case, and detail_status lets you filter records according to the information you need.

### Use cases for Bayt job data

- **Recruitment and sourcing.** Track which employers in the UAE, Saudi Arabia or Qatar advertise a role, and how often new vacancies for it appear.
- **Labour market analysis.** Compare demand for a skill between Gulf countries, cities and career levels, using the same keyword across several country runs.
- **Salary research.** Collect published salary ranges with their original currency and pay period from job details, kept separate from the converted card text.
- **Sales and partnerships.** Find companies that are hiring in a region, with their public Bayt employer profile links.
- **Job market dashboards.** Schedule the same search daily or weekly and join exports on the stable `job_id` to see which listings are new or have disappeared.

### How it compares with other Bayt jobs tools

Several Bayt scrapers and GCC jobs aggregators exist in the Apify Store. Before you choose, compare them on these points:

- **Billing unit.** This actor charges one event per delivered unique job and has no separate start event. Empty searches are free, and detail enrichment does not add a second charge per job.
- **Duplicates.** Bayt can repeat a vacancy on consecutive pages. Repeated job identifiers are removed within a run, so you do not pay twice for the same job.
- **Salary honesty.** The card salary is kept as displayed, and the structured amount, currency and period come only from the published detail. Nothing is converted or guessed.
- **Input you already have.** A Bayt search URL from your browser works as input, filters included, as do the field names used by older Bayt actors (`query`, `startUrl`, `maxResults`, `results_wanted`).
- **Scope.** This actor covers Bayt only. A multi-board aggregator gives broader coverage of other job sites, at the cost of fields that differ per source.

### Pricing

The actor's active Apify price is shown on its listing. The billing unit is one delivered unique job record. Optional detail enrichment belongs to the same record and does not introduce a second job charge. Empty searches deliver no records and incur no job event charges; the actor has no custom start event.

Non-paying accounts are limited to ten job records per run. The same cap applies to compatible input aliases. Set the result limit to the volume you need, and use details only when the additional description or metadata is useful for your analysis.

### FAQ

#### Can I search several Gulf countries?

Choose international for the broader catalogue, or run separate searches with the country field when you need country-specific filters. A pasted search URL controls the country for that run.

#### Why is a salary or job category missing?

These values depend on what the employer and Bayt publish. Structured salaries and job categories require details to be enabled. Missing source values remain null, and an unavailable detail page is reported explicitly.

#### Does the actor return contact details or apply to jobs?

It returns public vacancy information. It does not submit applications or extract private applicant information. Public employer profile links are included where Bayt publishes them.

#### Which countries does the Bayt Jobs Scraper support?

The United Arab Emirates, Saudi Arabia, Qatar, Kuwait, Bahrain, Oman, Egypt, Jordan, Lebanon, Morocco, Algeria, Tunisia, Libya, Iraq, Pakistan and India, plus the international search across all of them.

#### Why did my run return fewer records than max_results?

The source may contain fewer unique vacancies than your requested ceiling, some pages can repeat listings, or collection may be incomplete. Check the dataset count and the run's partial status and warnings.

### Example input and captured output

The following input returned 100 unique jobs in the captured search. This is a ceiling rather than a guarantee for future runs.

```json
{
  "country": "ae",
  "keyword": "software engineer",
  "max_results": 100
}
```

These selected fields come from the first job in that actual response:

```json
{
  "job_id": "5487344",
  "title": "Mobile Applications Growth Marketer/ ASO Specialist /Paid Ads Marketer",
  "company": "Beyond Limits",
  "location": "Dubai, UAE",
  "posted_at": "2026-09-28T09:18:34Z",
  "url": "https://www.bayt.com/en/uae/jobs/mobile-applications-growth-marketer-aso-specialist-paid-ads-marketer-5487344/"
}
```

With `include_details` set to true, the same job 5487344 adds these fields from its captured detail page:

```json
{
  "job_id": "5487344",
  "employment_type": "FULL_TIME",
  "job_category": "Marketing Executive",
  "salary_min": 8000,
  "salary_max": 9000,
  "salary_currency": "USD",
  "salary_period": "MONTH",
  "experience_months": 12,
  "valid_through": "2026-10-28T00:00:00Z",
  "detail_status": "ok"
}
```

# Actor input Schema

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

Bayt country ISO code or slug; defaults to ae. Supports ae, sa, qa, kw, eg, bh, om, jo, lb, ma, dz, tn, ly, iq, pk, in and international.

## `keyword` (type: `string`):

Search phrase, including Arabic text. Empty means all jobs in the selected country.

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

Optional city, such as Dubai or Riyadh.

## `search_url` (type: `string`):

HTTPS Bayt country search URL, including filters. Overrides country, keyword and city. A page parameter sets the starting page; Arabic page paths are read in English.

## `sort_by` (type: `string`):

Relevance is the default. When omitted, preserves sorting from search_url.

## `posted_within` (type: `string`):

Optional posting date filter. Preserves a pasted URL filter when omitted.

## `max_results` (type: `integer`):

Maximum unique jobs across all continuation requests; defaults to 100.

## `include_details` (type: `boolean`):

Fetch full descriptions, employment type, job category and original salary where Bayt publishes them. Defaults to false.

## `query` (type: `string`):

Alias for keyword. The canonical field takes precedence when supplied.

## `startUrl` (type: `string`):

Alias for search_url. The canonical field takes precedence when supplied.

## `startUrls` (type: `array`):

Alias for search_url: uses the first URL string or object with url; remaining entries are ignored.

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

Alias for max_results.

## `results_wanted` (type: `integer`):

Alias for max_results.

## `includeDetails` (type: `boolean`):

Alias for include_details.

## Actor input object example

```json
{
  "country": "ae",
  "max_results": 100
}
```

# Actor output Schema

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

Unique Bayt job rows.

## `output` (type: `string`):

Counts, warnings and continuation status.

# 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 = {
    "max_results": 100,
    "include_details": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/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 = {
    "max_results": 100,
    "include_details": False,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/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 '{
  "max_results": 100,
  "include_details": false
}' |
apify call s-r/bayt-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/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/VxeCLvQbAg4A7eBT1/builds/Ani3NmaOTy8I3mpgh/openapi.json
