# Indeed Jobs Scraper (`devcake/indeed-multi-country-scraper`) Actor

Extract job listings and optional company information from 44 Indeed country domains with advanced filtering

- **URL**: https://apify.com/devcake/indeed-multi-country-scraper.md
- **Developed by:** [devcake](https://apify.com/devcake) (community)
- **Categories:** Automation, Jobs, Developer tools
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 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

## Scrape Indeed Job Postings by Country

Pull fresh Indeed job listings from 44 country domains — UK, US, CA, AU, DE, IN, JP, BR, AE, and more — through a single input. The actor handles proxy routing, page parsing, full description enrichment, and salary normalization so you can pull market-ready job data in minutes rather than weeks. Built for recruiters, talent researchers, and HR-analytics teams who need clean cross-country Indeed data without writing scrapers.

### 💼 What the Actor Does

The Indeed Multi-Country Job Scraper pulls job cards, descriptions, and salary data from Indeed's local domains across 44 countries. You supply a query, a location, and a country; the actor fetches the listings page through a country-matched residential proxy, parses the embedded job data, batches up to 15 full descriptions per JSON request when details are turned on, and emits each job as a structured record. Country routing, 429 backoff, session rotation, and result-cap enforcement happen automatically so you can run unattended.

### 💼 Who Should Run This

- **Talent sourcing teams** — monitor competitor hiring across UK, US, DE, and AU for live lead generation.
- **HR-analytics and compensation teams** — pull salary bands across markets and convert to a single reporting currency.
- **Job-board aggregators and ATS vendors** — feed listings from multiple Indeed domains into your own product.
- **Labor-market researchers** — track role demand, posting velocity, and skill mentions across countries.
- **First-time international job seekers** — verify role availability, salary ranges, and visa-sponsorship language across 44 markets.

### 💼 Data You Receive Per Job

| Field | What It Means |
|---|---|
| `job_key` | Indeed's stable identifier for the posting |
| `title`, `company`, `location`, `url` | The listing card content |
| `salary`, `salary_min`, `salary_max`, `salary_currency`, `salary_type` | Raw salary text plus parsed min / max / period |
| `description`, `benefits`, `job_type`, `posted_date` | Full-detail fields returned when `includeJobDetails: true` |
| `salary_min_converted`, `salary_max_converted`, `salary_currency_converted` | Salary converted to a target ISO currency like USD, EUR, GBP |
| `search_query`, `search_location`, `country`, `domain`, `scraped_at` | Search metadata for traceability |
| `change_type`, `first_seen`, `previous_*` | Incremental-mode fields for NEW, UPDATED, UNCHANGED, REAPPEARED jobs |

### 💼 Common Ways Teams Run This Actor

Run the actor in card-only mode for market scans and lead lists. Turn on `includeJobDetails` when you need full descriptions and benefits. Add `targetCurrency` to normalize salary fields into a single reporting currency. Enable `incrementalMode` between runs to detect NEW, UPDATED, and EXPIRED jobs against the previous snapshot. Combine filters — `remote`, `jobType`, `experienceLevel`, `fromAge` — to scope the result to the role profile you actually need.

### 💼 Quick Start

1. Pick a **country** from the dropdown — every listed country has a matching residential proxy pre-routed.
2. Add one or more **search keywords** like `software engineer`, `data scientist`, or `registered nurse`.
3. Optionally add **locations** like `London` or `Remote`. Leave empty to search the whole country.
4. Set **maxResults** (default 50, 0 = unlimited) to bound the run — it is the only cap that affects how many jobs you pay for.
5. Open **Output Options**, tick **Fetch Full Job Details** if you want descriptions, and set **targetCurrency** if you want salary normalization.
6. Click **Run** and watch the dataset populate. The summary record in the key-value store shows total items, duration, and any per-query errors.

### 💼 How Do I Monitor Indeed Job Postings Across Multiple Countries?

Run the actor once per country, or chain country-specific tasks behind a workflow. Each run emits a dataset with a `country` field and a `domain` field — concatenate these across runs and you have a single global job feed. For continuous monitoring, enable `incrementalMode` so each new run reports only NEW, UPDATED, UNCHANGED, and REAPPEARED jobs against the prior snapshot.

### 💼 How Can I Benchmark Indeed Salaries Across Markets?

Turn on `includeJobDetails` and set `targetCurrency` to your reporting currency (USD, EUR, GBP, JPY, INR, etc.). The actor parses Indeed's salary strings into `salary_min`, `salary_max`, and `salary_currency`; when a `targetCurrency` is set, it also adds `salary_min_converted`, `salary_max_converted`, and `salary_currency_converted` using cached FX rates. Filter by `fromAge` (for example 14 days) to keep salary figures tied to fresh postings.

### 💼 How Do I Track a Specific Role Across Indeed Country Sites?

Create one task per country using the same query string. For example, run `data scientist` against `us`, `uk`, `ca`, `au`, `de`, and `sg` to compare compensation and volume across markets. Set `globalDeduplication: true` to drop duplicate job keys across pages within a run, and pull the resulting dataset into a spreadsheet or BI tool for side-by-side comparison.

### 💼 How Do You Scrape Indeed Job Postings Without Getting Blocked?

The actor uses `httpcloak` for browser-compatible TLS and HTTP fingerprints, matches a residential proxy to the selected country, negotiates Brotli on listings and gzip on JSON detail responses, and applies an adaptive delay with rotation on 429s. By default it picks the `chrome-latest` desktop fingerprint and rotates through mobile presets when `useMobileHeaders` is on. You do not need to manage cookies or proxy rotation manually.

### 💼 Input Parameters

| Field | Default | Purpose |
|---|---|---|
| `country` | `uk` | Selects the Indeed domain and proxy country |
| `queries` | `[]` | Search terms; empty runs an all-jobs scan |
| `locations` | `[]` | Cities, regions, or `Remote`; empty searches everywhere |
| `maxResults` | `50` | Run-wide emitted-item cap; `0` = unlimited |
| `sort` | `relevance` | `relevance` or `date` |
| `fromAge` | `0` | Posted within N days; `0` disables it |
| `jobType`, `remote`, `experienceLevel` | `Any` | Filter by full-time, part-time, Remote, Hybrid, On-site, Entry, Mid, Senior |
| `excludeStaffingAgencies` | `false` | Hides jobs posted by recruitment agencies |
| `includeJobDetails` | `false` | One batched JSON detail request per page; HTML fallback |
| `targetCurrency` | empty | Converts parsed min / max salary to a target ISO currency |
| `enrichCompany` | `false` | Adds logo, industry, and size from Indeed company pages |
| `requestDelay` | `1` | Base inter-page delay before jitter and adaptive backoff |
| `useMobileHeaders` | `false` | Mobile fingerprint chain (`chrome-latest-android` first) |
| `incrementalMode`, `emitUnchanged`, `stateKey` | off | Persistent state for NEW / UPDATED / UNCHANGED / EXPIRED detection |

### 💼 Example Run

**Input**

```json
{
  "country": "uk",
  "queries": ["software engineer", "data scientist"],
  "locations": ["London", "Remote"],
  "maxResults": 200,
  "includeJobDetails": true,
  "targetCurrency": "GBP",
  "fromAge": 7,
  "sort": "date"
}
```

**Output (excerpt)**

```json
{
  "job_key": "1f0c2c8e7a5b6d3f",
  "title": "Senior Backend Engineer",
  "company": "Example Tech Ltd",
  "location": "London",
  "url": "https://uk.indeed.com/viewjob?jk=1f0c2c8e7a5b6d3f",
  "salary": "£90,000 - £120,000 a year",
  "salary_min": 90000,
  "salary_max": 120000,
  "salary_currency": "GBP",
  "salary_min_converted": 114150,
  "salary_max_converted": 152200,
  "salary_currency_converted": "USD",
  "search_query": "software engineer",
  "search_location": "London",
  "country": "uk",
  "domain": "uk.indeed.com",
  "scraped_at": "2026-07-20T11:42:18Z"
}
```

### 💼 FAQ

#### Is it legal to scrape Indeed job postings?

Public Indeed listings are intended to be browsed. This actor fetches the same public HTML pages a user would visit, respects your country's terms of service, and adds a default 1-second delay between pages. You are responsible for ensuring your use case complies with Indeed's terms and applicable law.

#### Can I scrape Indeed from multiple countries in one run?

Yes. Pick a country from the dropdown; the actor routes through a country-matched residential proxy and applies that country's Indeed domain. To aggregate across markets, schedule one task per country or use Apify's workflow chaining.

#### Does the actor include full job descriptions?

Only when you enable `includeJobDetails`. Card-only mode runs faster and cheaper; detail mode adds one batched JSON request per listings page plus HTML fallback when the JSON path fails. Descriptions add roughly 5 KB decoded per enriched job.

#### How fresh is the data?

Freshness equals Indeed's. With `fromAge: 7` and `sort: date`, the run returns jobs posted in the last seven days in date order. Use `fromAge: 0` with no date filter for the full dataset.

#### Will I get blocked?

The actor uses browser-compatible TLS and HTTP fingerprints, residential proxies matched to the country, Brotli on listings, gzip on JSON, and adaptive delay with rotation on 429s. If you still hit resistance, raise `requestDelay` to 3 or 5 seconds, turn on `useMobileHeaders`, or supply your own proxy via `customProxy`.

#### Does it work for non-English countries?

Yes. Country-specific Indeed domains (uk, de, fr, jp, br, mx, ae, sa, sg, in, kr, tw, hk, my, th, ph, id, vn, za, eg, il, ie, nz, etc.) are all routed through a residential proxy in that country. Salary strings are parsed against common European, Asian, and Middle-Eastern formats and converted to your target currency when requested.

# Actor input Schema

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

Pick the country you want to search. Proxy country is matched automatically.

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

Add one or more job titles or skills. Leave empty to search all jobs.

## `locations` (type: `array`):

Add cities, regions, or 'Remote'. Leave empty to search everywhere.

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

Stop after this many jobs across all searches. Use 0 for unlimited.

## `sort` (type: `string`):

Order results by Relevance or Most Recent.

## `fromAge` (type: `integer`):

Only show jobs posted in the last N days. 0 = no limit.

## `jobType` (type: `string`):

Restrict to one employment type.

## `remote` (type: `string`):

Restrict to Remote Only, Hybrid, or On-site Only.

## `experienceLevel` (type: `string`):

Restrict to Entry, Mid, or Senior Level.

## `excludeStaffingAgencies` (type: `boolean`):

Hide jobs posted by recruitment agencies.

## `includeJobDetails` (type: `boolean`):

Add descriptions, benefits, and posted dates via one batched JSON request per page.

## `targetCurrency` (type: `string`):

ISO code like USD, EUR, GBP. Leave empty to keep the original currency.

## `enrichCompany` (type: `boolean`):

Fetch logo, industry, and size from Indeed company pages. Most expensive option.

## `requestDelay` (type: `integer`):

Seconds to wait between pages. Lower = faster but more risk of rate limits.

## `incrementalMode` (type: `boolean`):

Save seen jobs and flag NEW / UPDATED / EXPIRED on each run.

## `emitUnchanged` (type: `boolean`):

Include unchanged jobs in the dataset. Only effective with Track Changes enabled.

## Actor input object example

```json
{
  "country": "uk",
  "queries": [
    "software engineer"
  ],
  "locations": [
    "London"
  ],
  "maxResults": 50,
  "sort": "relevance",
  "fromAge": 0,
  "jobType": "",
  "remote": "",
  "experienceLevel": "",
  "excludeStaffingAgencies": false,
  "includeJobDetails": false,
  "targetCurrency": "",
  "enrichCompany": false,
  "requestDelay": 1,
  "incrementalMode": false,
  "emitUnchanged": false
}
```

# Actor output Schema

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

Default landing view — title, company, location, parsed salary, and apply link

## `jobs` (type: `string`):

Complete dataset of all scraped job listings with raw + parsed salary, search context, and timestamps

## `salary` (type: `string`):

Side-by-side raw and target-currency salary columns; useful when targetCurrency is set

## `changes` (type: `string`):

Change-tracking fields (NEW / UPDATED / UNCHANGED / REAPPEARED) — only meaningful when incrementalMode is true

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

Statistics and metadata about the scrape

# 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": [
        "software engineer"
    ],
    "locations": [
        "London"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("devcake/indeed-multi-country-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": ["software engineer"],
    "locations": ["London"],
}

# Run the Actor and wait for it to finish
run = client.actor("devcake/indeed-multi-country-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": [
    "software engineer"
  ],
  "locations": [
    "London"
  ]
}' |
apify call devcake/indeed-multi-country-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,devcake/indeed-multi-country-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/J21U9wuCwVeRR0ewn/builds/neQcEcgx5xqdJ30Yl/openapi.json
