How to scrape Bayt.com job postings across the Gulf and MENA
Last updated
Bayt.com has no public jobs API, so the practical route is Datapika's job board scraper on Apify with bayt in the sites list. Bayt is a keyword-only board: the scraper honors searchTerm and up to 5 searchTerms, and ignores location, hoursOld, jobType and remote filters, so the city or country belongs inside the query itself. Bayt pages are rendered in a real browser, which puts a typical run at 1 to 3 minutes, and each posting lands as one row with title, company, location, job URL and the full description. Pricing is $0.005 per job delivered, about $5 per 1,000.
Which countries and roles does a Bayt scraper cover?
Bayt.com is the regional board the scraper uses for the Middle East, and its own app listing describes 58 million job seekers and 60,000+ employers across the UAE, Saudi Arabia, Qatar, Kuwait, Egypt, Jordan, Lebanon and the wider MENA region since 2000. That makes it the natural source for Gulf roles that never reach LinkedIn or Indeed: Saudi construction supervisors, Dubai retail managers, Qatar hospitality staff, Egyptian accountants posted by a Riyadh employer. Bayt operates in English and Arabic, and the scraper stores the description exactly as the employer wrote it, so a single dataset can mix Latin and Arabic script and your downstream filters should tolerate both. Because Bayt has no location input on the scraper side, coverage is steered purely by the words you search. A query such as "HSE officer Saudi Arabia" narrows to one country, while "HSE officer" alone returns the board's regional ranking. Visa sponsorship and nationality preferences are not separate fields; when an employer states them, they appear in the description text and can be extracted with a simple keyword pass.
- Regional scope per Bayt's own listing: UAE, Saudi Arabia, Qatar, Kuwait, Egypt, Jordan, Lebanon and the wider MENA region
- 58 million job seekers and 60,000+ employers on the platform, operating since 2000
- Postings can be in English, Arabic, or a mix; descriptions arrive unchanged in Markdown or HTML
- No location input applies to Bayt, so put the country or city inside the search term
- Visa, sponsorship and nationality requirements live in the description text, not in dedicated columns
- Pair Bayt with Naukri in one run to cover the Gulf and India expat corridor together
What fields does each Bayt job row contain?
Every Bayt posting becomes one dataset item that shares the same column layout as the other boards, with site set to bayt so rows can be filtered after a multi-board sweep. The reliably populated core is id, title, company, location, job_url, date_posted, description, search_term, matched_search_term and scraped_at. The description is the full posting body in Markdown by default or HTML if you set descriptionFormat, and emails found inside it are copied into the emails array, which matters whenever a Bayt employer asks applicants to write in directly. Salary columns are filled when the posting states a figure: salary_source reports whether the number came from structured board data or was parsed out of the description, alongside salary_min, salary_max, salary_currency and salary_interval, and enforceAnnualSalary converts monthly AED or SAR figures to yearly equivalents. Columns that the README ties to other boards stay null on Bayt rows: job_level is LinkedIn only, company_country is Indeed only, and skills, experience_range, vacancy_count and work_from_home_type are Naukri only. The table below marks what to expect column by column.
- Core on every row: id, title, company, location, job_url, site, date_posted, description, scraped_at
- matched_search_term shows which of your up-to-5 queries surfaced the posting
- emails array captures contact addresses embedded in the posting text
- Salary quartet (min, max, currency, interval) plus salary_source when the employer states pay
- enforceAnnualSalary turns monthly Gulf salaries into yearly figures for comparison
- job_level, company_country and the four Naukri columns are null on Bayt rows
What are Bayt's quirks compared with the other boards?
Bayt behaves differently from the URL-parameter boards, and knowing the differences avoids empty or overpriced runs. First, it is the board where searchTerm is the sole filter: location, distance, isRemote, jobType, hoursOld, easyApply and offset are all ignored for Bayt, so a run with hoursOld: 24 still returns Bayt's default ordering rather than fresh postings. Second, Bayt is fetched through a stealth browser rather than a plain HTTP request, which is why its rows land after 1 to 3 minutes while Indeed and LinkedIn stream in within 5 to 20 seconds; runs default to 4 GB of memory to give the browser headroom. Third, freshness has to be handled on your side: keep the date_posted column, store the id of every row, and diff successive runs to isolate new postings. Fourth, the language mix means a search term in English may not surface a posting written only in Arabic, so bilingual sweeps need both spellings as separate entries in searchTerms.
- searchTerm is the only filter Bayt honors; every other input is ignored for this board
- Browser-rendered board: expect 1 to 3 minutes, versus 5 to 20 seconds for Indeed and LinkedIn
- Runs default to 4 GB memory so the browser-based boards have headroom
- No hoursOld effect on Bayt, so diff id values between scheduled runs to find new postings
- Add an Arabic spelling of the role as a second search term to catch Arabic-only postings
- Cross-board deduplication merges a Bayt posting that also appears on LinkedIn into one row
How many Bayt jobs can one run return, and how fast?
The ceiling is set by two inputs. maxResults caps each board at 100 rows per search term, and searchTerms accepts up to 5 queries, so a Bayt-only run tops out at 500 rows. Because Bayt ignores offset, paging beyond that means changing the query: split a broad title into per-country variants such as "procurement manager UAE", "procurement manager Saudi Arabia" and "procurement manager Qatar", or by seniority. Results stream to the dataset as each board finishes, so in a mixed run you can already read Indeed rows while the Bayt browser session is still loading; a verified default-style run produced its first dataset rows 3 seconds after the container started. For a Bayt-only run the wait is the browser warm-up plus page loads, typically 1 to 3 minutes end to end. Keep maxResults at 20, the default, until the query returns the roles you want. The actor has 27,458 runs and 2,471 users on Apify as of August 29, 2026, with 381 active in the last 30 days.
- maxResults: 1 to 100 per board per search term; searchTerms: up to 5 queries
- Bayt-only maximum per run: 500 rows; widen coverage by varying the country in the term
- offset is ignored on Bayt, so pagination happens through query variants
- First rows in a mixed run appear about 3 seconds after container start; Bayt rows follow in 1 to 3 minutes
- 27,458 runs, 2,471 users, 381 active in the last 30 days, 5.0 rating from 3 reviews (Apify, August 29, 2026)
- Default maxResults of 20 keeps test runs small while you tune the search term
How do you call the Bayt scraper from code or an AI agent?
The input is a single JSON object, and the Bayt-only version is short: searchTerm or searchTerms, sites set to ["bayt"], and maxResults. From the Apify Console you paste that into the form, run, and download the dataset as JSON, CSV, Excel, XML, HTML or RSS. From code, POST the same object to the run-sync-get-dataset-items endpoint for openclawai~job-board-scraper with your Apify token and the response body is the array of Bayt rows; the Python client wraps this as client.actor("openclawai/job-board-scraper").call(run_input=...), and the Node client mirrors it with apify-client. For agents, add https://mcp.apify.com/?tools=fetch-actor-details,openclawai/job-board-scraper to Claude Desktop, Cursor or any MCP client and the assistant can answer prompts like "which Dubai employers are hiring civil engineers on Bayt this week" by calling the tool and reasoning over the rows. Zapier, Make and n8n connect through the Apify app, and a daily schedule with a webhook turns the run into a standing Gulf job feed. The same run can sweep LinkedIn, Indeed, Glassdoor, ZipRecruiter and Naukri alongside Bayt; see the full board list at /scrape.
- Minimal input: {"searchTerms": ["nurse Riyadh", "nurse Dubai"], "sites": ["bayt"], "maxResults": 50}
- REST: POST to run-sync-get-dataset-items for openclawai~job-board-scraper and parse the returned array
- Python: pip install apify-client, then client.actor("openclawai/job-board-scraper").call(run_input=...)
- MCP: https://mcp.apify.com/?tools=fetch-actor-details,openclawai/job-board-scraper for Claude, ChatGPT or Cursor
- Zapier, Make and n8n push new rows to Sheets, Airtable, Slack or a CRM on a schedule
| Field | Populated on Bayt? | Notes |
|---|---|---|
| id, title, company, location | Yes | Board-issued job ID plus the headline fields; location is the string Bayt shows, often city and country |
| job_url | Yes | Link to the Bayt posting; site is set to bayt on every row |
| job_url_direct | When available | Direct apply URL on the employer site; null when the posting is applied to on Bayt itself |
| date_posted | Yes | Posting date; use it with id to diff scheduled runs, since hoursOld does not apply to Bayt |
| description | Yes | Full posting body in Markdown or HTML, in English, Arabic or a mix as written by the employer |
| emails | When present in text | Addresses extracted from the description when an employer asks for direct applications |
| salary_min, salary_max, salary_currency, salary_interval, salary_source | When the posting states pay | salary_source is direct_data or description; enforceAnnualSalary converts monthly figures to yearly |
| job_type, is_remote | When stated | Employment type and remote flag as detected; note the isRemote and jobType inputs do not filter Bayt |
| company_industry, company_url, company_logo, company_description, company_num_employees, company_revenue, company_rating, company_reviews_count | Varies | README marks company enrichment as board-dependent; treat these as optional on Bayt and do not build joins on them |
| job_level, company_country | No | job_level is LinkedIn only and company_country is Indeed only |
| skills, experience_range, vacancy_count, work_from_home_type | No | Naukri only; Bayt requirements such as experience or visa status stay inside description |
| search_term, matched_search_term, scraped_at | Yes | Run query, the specific term that surfaced the row, and the ISO scrape timestamp |
How to do it
- 1.Open https://apify.com/openclawai/job-board-scraper, set sites to ["bayt"], and enter a searchTerm that includes the country or city, for example "finance manager Dubai".
- 2.Add up to 4 more variants in searchTerms (other Gulf countries, an Arabic spelling of the title) and set maxResults between 20 and 100 per term.
- 3.Start the run and wait 1 to 3 minutes for the browser session to finish; rows appear in the dataset with site = bayt and a matched_search_term per row.
- 4.Export as JSON or CSV, or fetch the rows through the Apify API, then schedule the run daily and diff id values to isolate new postings.
Questions, answered
Does Bayt have an official API?
No public one. Bayt.com offers no developer program for reading job listings, and third-party scraper listings on Apify state plainly that no official public API exists. Employers post through Bayt's own dashboard, so reading listings programmatically means scraping the public search pages, which is what this actor does without a login, an API key or a partner agreement. The rows you get carry the same title, company, location and description you would see in a browser.
Can I filter Bayt jobs by location, date, or remote status?
Not through the scraper inputs. Bayt honors only searchTerm and searchTerms; location, distance, hoursOld, jobType, isRemote, easyApply and offset are ignored for this board. Put the country or city in the query, for example "electrical engineer Kuwait", and handle freshness after the fact by comparing date_posted and id across scheduled runs. The same inputs still work on LinkedIn, Indeed and Glassdoor within a mixed run.
Does the scraper return Arabic postings from Bayt?
It returns whatever the posting contains. Bayt operates in English and Arabic, and the description column stores the employer's text as written, so rows can be in English, Arabic or both. Matching is keyword based, so an English search term may not surface a listing written only in Arabic; add the Arabic job title as a separate entry in searchTerms and both sets of rows arrive in the same dataset with matched_search_term telling them apart.
How much does it cost to scrape 1,000 Bayt jobs?
$5, at $0.005 per job row delivered to your dataset, with no monthly fee. A Bayt-only run is capped at 500 rows (100 per term times 5 terms), so the largest single run costs $2.50, and a run that returns nothing costs only Apify's small start fee. Apify's free-plan monthly credit covers roughly 1,000 Bayt postings before you pay anything.
Are visa, sponsorship, or nationality requirements available as fields?
Not as dedicated columns. The dataset schema has no visa or nationality field for any board, so when a Gulf employer specifies sponsorship, a preferred nationality, or a required residency, the statement sits inside the description text. Because the description arrives complete in Markdown, a keyword match for terms like "visa", "sponsorship" or a nationality name, or a short LLM pass over the text, extracts it reliably after the run.
Why do Bayt rows take longer to appear than Indeed rows?
Bayt is one of the boards fetched through a real browser with anti-bot warm-up, along with Glassdoor, ZipRecruiter and Naukri, so its pages typically take 1 to 3 minutes to load and parse. Indeed and LinkedIn are read over plain requests and land within 5 to 20 seconds. Since v1.0.49 each board pushes its rows the moment it finishes, so a mixed run lets you read fast boards while Bayt is still rendering.