# GCC Jobs API - Direct Employers, ATS, Visa Signals & Alerts (`fetchfinch/gcc-jobs-intelligence`) Actor

Search direct employer career sites across the GCC. Get canonical apply links, normalized hiring requirements, evidence-backed GCC signals, deduplication, and incremental monitoring.

- **URL**: https://apify.com/fetchfinch/gcc-jobs-intelligence.md
- **Developed by:** [Fetch Finch](https://apify.com/fetchfinch) (community)
- **Categories:** Jobs, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 verified gcc job results

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/platform/actors/running/actors-in-store#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

## GCC Jobs API: Direct Employers, ATS, Visa Signals & Alerts

Collect GCC vacancies directly from employer career systems, normalize them into one stable schema, and optionally receive only meaningful changes on scheduled runs.

This Actor is built for recruitment intelligence, job-search products, market research, and automation workflows that need more than shallow search cards. Every result retains its employer-source provenance and canonical application link. Parsed GCC-specific signals include the evidence used to produce them.

### Why use this Actor?

- **Direct-employer data:** public Greenhouse, Lever, Ashby, and SmartRecruiters job-posting APIs, not scraped copies of aggregator listings.
- **GCC-aware normalization:** UAE, Saudi Arabia, Qatar, Kuwait, Bahrain, and Oman country/city handling, with structured ISO fields preferred over ambiguous text.
- **Useful candidate signals:** visa/work-authorization language, Arabic requirements, nationality eligibility, relocation, regulated licenses, experience, salary, skills, function, and seniority.
- **Evidence, not mystery labels:** important inferred fields include confidence, source/parsed/inferred kind, and exact supporting text offsets.
- **Incremental monitoring:** emit `NEW`, `UPDATED`, `REAPPEARED`, or `EXPIRED` jobs; unchanged jobs are free unless explicitly requested.
- **Safe expiration behavior:** a job is never expired because an ATS failed, timed out, or returned a truncated snapshot.
- **Transparent health:** every run writes per-source request, retry, completeness, result, and error diagnostics to `OUTPUT`.
- **Automation-friendly:** standard, compact/MCP, and raw output modes plus versioned IDs and deterministic field hashes.

### Source coverage

| Provider | Collection method | Typical strengths |
| --- | --- | --- |
| Greenhouse | Public Job Board API | Full descriptions, departments, offices, requisition IDs, publication/update times |
| Lever | Public Postings API | Structured location/country, teams, workplace and employment types, direct apply URLs |
| Ashby | Public Job Postings API | Primary/secondary locations, workplace type, plain/HTML descriptions, compensation objects |
| SmartRecruiters | Public company postings API | Country-filtered pagination, structured location, reference and job-ad sections |

The built-in launch registry currently contains the four continuously tested source boards below. Add any supported employer through `careerUrls`; the Actor detects the provider and board token automatically.

| Employer key | Provider | Expected GCC coverage |
| --- | --- | --- |
| `alphasights` | Greenhouse | UAE |
| `trendyol` | Lever | UAE, Saudi Arabia |
| `cohere` | Ashby | UAE, Saudi Arabia, Qatar / Middle East roles |
| `aecom` | SmartRecruiters | UAE, Saudi Arabia, Qatar, Oman |

A live UAE validation on August 12, 2026 fetched 414 active source records and emitted 162 GCC matches before high-precision deduplication. Counts change as employers publish and close vacancies.

### Quick start

Search every built-in employer for recently posted UAE and Saudi engineering roles:

```json
{
  "keywords": ["software engineer", "data engineer", "platform engineer"],
  "countries": ["AE", "SA"],
  "postedWithinDays": 30,
  "includeDetails": true,
  "outputMode": "standard",
  "maxResults": 500
}
```

Search selected employers and add your own supported career boards:

```json
{
  "countries": ["AE", "SA", "QA"],
  "employers": ["cohere", "trendyol"],
  "careerUrls": [
    "https://boards.greenhouse.io/YOUR_BOARD",
    "https://jobs.lever.co/YOUR_COMPANY",
    "https://jobs.ashbyhq.com/YOUR_BOARD",
    "https://jobs.smartrecruiters.com/YOUR_COMPANY"
  ],
  "includeKeywords": ["typescript", "python"],
  "excludeKeywords": ["intern"],
  "workplaceTypes": ["hybrid", "remote"]
}
```

Create a quiet daily monitor that establishes its first snapshot without emitting billable rows:

```json
{
  "countries": ["AE"],
  "cities": ["Dubai", "Abu Dhabi"],
  "monitorMode": true,
  "stateKey": "uae-tech-daily",
  "baselineMode": "state_only",
  "changeTypes": ["NEW", "UPDATED", "REAPPEARED", "EXPIRED"],
  "missingRunsBeforeExpiry": 2,
  "outputMode": "compact"
}
```

For later scheduled runs, keep the same filters and `stateKey`, and change `baselineMode` to `emit` if desired. The query fingerprint is part of the state namespace, preventing different watchlists from overwriting each other.

### Input

| Field | Meaning |
| --- | --- |
| `keywords` | Match any phrase against normalized title and description |
| `countries` | GCC ISO codes: `AE`, `SA`, `QA`, `KW`, `BH`, `OM` |
| `cities` | Optional normalized city filter |
| `sources` | Any of `greenhouse`, `lever`, `ashby`, `smartrecruiters` |
| `employers` | Built-in registry keys; empty searches all compatible entries |
| `careerUrls` | Additional supported ATS board or job URLs |
| `postedWithinDays` | Publication-date window; `0` disables it |
| `includeKeywords` / `excludeKeywords` | Post-normalization content filters |
| `workplaceTypes` | `onsite`, `hybrid`, and/or `remote` |
| `employmentTypes` | Normalized full-time, part-time, contract, temporary, internship, or other values |
| `includeDetails` | Retain/fetch descriptions needed for evidence and richer parsing |
| `outputMode` | `standard`, `compact`, or `raw` |
| `maxResults` | Hard cap on emitted, billable rows |
| `maxResultsPerSource` | Per-board safety cap; reaching it disables expiry for that board |
| `monitorMode` | Enable persistent change detection |
| `stateKey` | Human-readable watchlist state name |
| `changeTypes` | Monitoring events to emit |
| `baselineMode` | `emit` or free `state_only` first snapshot |
| `missingRunsBeforeExpiry` | Complete successful absences required before `EXPIRED` (default `2`) |
| `requestTimeoutSecs` / `maxConcurrency` | Network controls |
| `proxyConfiguration` | Optional Apify proxy settings; normally unnecessary for public ATS APIs |

### Output

Each standard job record contains:

- stable `jobId`, cross-source `clusterId`, and `schemaVersion`;
- `changeType`, `changedFields`, `firstSeenAt`, and `lastSeenAt`;
- title, normalized company, structured locations, work mode, employment type, department, and dates;
- typed `experience` and `salary`, including normalized monthly salary when the period is known;
- extracted `skills` and GCC-specific signals with confidence and evidence;
- deterministic function/seniority classification with evidence;
- canonical employer application URL and every retained source reference;
- completeness score and warnings.

Example signal:

```json
{
  "arabic": {
    "value": "required",
    "confidence": 0.95,
    "kind": "parsed",
    "evidence": [
      {
        "text": "Arabic is required",
        "start": 2433,
        "end": 2451,
        "field": "description",
        "rule": "arabic-required"
      }
    ]
  }
}
```

`compact` mode retains the fields most useful to MCP clients and LLM workflows. `raw` mode adds the original source payload. Raw mode uses more dataset storage and should be enabled only when source-level debugging or archival is required.

The default key-value store's `OUTPUT` record contains the run summary:

```json
{
  "status": "SUCCEEDED",
  "counts": {
    "sourcesRequested": 4,
    "sourcesSucceeded": 4,
    "rawFetched": 414,
    "afterGccFilter": 162,
    "emitted": 162
  },
  "sourceHealth": [
    {
      "sourceKey": "greenhouse:alphasights",
      "status": "SUCCEEDED",
      "complete": true,
      "requests": 1,
      "retries": 0,
      "jobsFetched": 87,
      "gccJobs": 2,
      "error": null
    }
  ]
}
```

### Monitoring semantics

Monitoring state is stored in the named `gcc-jobs-monitor-state-v1` key-value store. A short lease rejects overlapping runs for the same query rather than racing two state writes.

- `NEW`: no prior source identity exists.
- `UPDATED`: the same job exists and one or more normalized fields changed.
- `REAPPEARED`: a previously expired job is visible again.
- `EXPIRED`: every source previously associated with the job completed successfully and omitted it for the configured number of runs.
- `UNCHANGED`: the normalized content hash is the same. It is not emitted by default.

State is committed only after all intended rows are saved. If the run's maximum charge limit prevents the full result from being emitted, state is not advanced, so unseen changes are not lost.

### Pricing behavior

The Actor is designed for Apify pay-per-event pricing using the standard `apify-default-dataset-item` event. One saved, deduplicated job or change event is one billable result.

- Filtered jobs, exact duplicates, source failures, and the `OUTPUT` summary are not billed.
- A cross-source cluster is billed once.
- Unchanged monitoring records are not billed unless `UNCHANGED` is requested.
- A `state_only` baseline produces no dataset items.
- The Actor checks the run's maximum total charge before writing data and avoids committing partial monitor state.

Suggested launch price: **$0.0015–$0.0020 per emitted item** ($1.50–$2.00 per 1,000). The final Store price and tiering are configured on the Actor's Monetization page.

### Local development

```bash
npm install
npm test
npm run typecheck
npm run build
apify validate-schema
apify run --purge --input '{"countries":["AE"],"employers":["alphasights"],"maxResults":10}'
```

Local output is written to `storage/datasets/default` and `storage/key_value_stores/default/OUTPUT.json`.

### Reliability and source policy

Requests use keep-alive HTTP, bounded concurrency, timeouts, and retry/backoff for rate limits and transient server errors. Sources fail independently. A partial run can still return healthy-source data while reporting exactly what failed.

This launch version uses documented or purpose-built public employer job-posting endpoints. It does not scrape Bayt, GulfTalent, NaukriGulf, Foundit, LinkedIn, Indeed, or Glassdoor. Those portals require an explicit permission/licensing decision before being added to a public paid product.

The implementation and commercial research behind these choices is included in `gcc-jobs-aggregator-implementation-research-2026-08-12.md`.

# Actor input Schema

## `keywords` (type: `array`):

Keep jobs matching any phrase in the title or description. Leave empty for all roles.

## `countries` (type: `array`):

One or more GCC countries. Structured ISO locations take precedence over text matching.

## `cities` (type: `array`):

Optional city filter, for example Dubai, Abu Dhabi, Riyadh, or Doha.

## `sources` (type: `array`):

Verified public job-posting APIs to query.

## `employers` (type: `array`):

Optional employer keys from the maintained registry. Empty means every compatible registry employer.

## `careerUrls` (type: `array`):

Greenhouse, Lever, Ashby, or SmartRecruiters board/job URLs. The Actor detects the provider and board token.

## `postedWithinDays` (type: `integer`):

Keep jobs published within this many days. Set 0 to disable the date filter.

## `includeKeywords` (type: `array`):

Require at least one phrase in the title or description.

## `excludeKeywords` (type: `array`):

Drop jobs containing any phrase in the title or description.

## `workplaceTypes` (type: `array`):

Optional on-site, hybrid, or remote filter. Unknown values are excluded when this filter is set.

## `employmentTypes` (type: `array`):

Optional normalized employment-type filter. Unknown source values are excluded when this filter is set.

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

Fetch or retain full job descriptions where the public source provides them.

## `outputMode` (type: `string`):

Standard returns normalized details and evidence; compact is optimized for MCP and LLM contexts; raw also retains source payloads.

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

Hard cap on billable dataset rows across the run.

## `maxResultsPerSource` (type: `integer`):

Safety cap per employer board. Reaching it marks that board partial and disables expiry detection for it.

## `monitorMode` (type: `boolean`):

Persist state and classify jobs as new, updated, reappeared, unchanged, or expired.

## `stateKey` (type: `string`):

Stable name for this watchlist. Query filters are fingerprinted automatically to prevent collisions.

## `changeTypes` (type: `array`):

Choose which monitoring events become billable dataset rows.

## `baselineMode` (type: `string`):

Emit returns the initial snapshot. State only creates a free baseline with no dataset rows.

## `missingRunsBeforeExpiry` (type: `integer`):

A job is expired only after this many complete, successful source runs no longer contain it.

## `requestTimeoutSecs` (type: `integer`):

Timeout for an individual public API request.

## `maxConcurrency` (type: `integer`):

Maximum number of simultaneous job-detail requests within a source.

## `proxyConfiguration` (type: `object`):

Optional proxy. Public ATS APIs normally work without one.

## Actor input object example

```json
{
  "keywords": [],
  "countries": [
    "AE",
    "SA",
    "QA",
    "KW",
    "BH",
    "OM"
  ],
  "cities": [],
  "sources": [
    "greenhouse",
    "lever",
    "ashby",
    "smartrecruiters"
  ],
  "employers": [],
  "careerUrls": [],
  "postedWithinDays": 0,
  "includeKeywords": [],
  "excludeKeywords": [],
  "workplaceTypes": [],
  "employmentTypes": [],
  "includeDetails": true,
  "outputMode": "standard",
  "maxResults": 1000,
  "maxResultsPerSource": 500,
  "monitorMode": false,
  "stateKey": "default",
  "changeTypes": [
    "NEW",
    "UPDATED",
    "REAPPEARED",
    "EXPIRED"
  ],
  "baselineMode": "emit",
  "missingRunsBeforeExpiry": 2,
  "requestTimeoutSecs": 30,
  "maxConcurrency": 8,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `details` (type: `string`):

No description

## `runSummary` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("fetchfinch/gcc-jobs-intelligence").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("fetchfinch/gcc-jobs-intelligence").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 '{}' |
apify call fetchfinch/gcc-jobs-intelligence --silent --output-dataset

```

## MCP server setup

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

```

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/0fGOA6H8njkhlWdsB/builds/UbYdEzVM6htpUVww3/openapi.json
