# Indeed Job Scraper (`herus13/indeed-scraper`) Actor

Scrape Indeed job listings across 59 country storefronts by keyword or search URL. Every row carries title, company_name, salary_min, salary_max, latitude, longitude, skills, benefits and the full description_text. Export JSON, CSV or Excel, or pull it from the API.

- **URL**: https://apify.com/herus13/indeed-scraper.md
- **Developed by:** [herus13](https://apify.com/herus13) (community)
- **Categories:** Jobs, Lead generation
- **Features:** Limited permissions
- **Stats:** 12 total users, 5 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

Indeed Job Scraper extracts job listings from [Indeed](https://www.indeed.com) by keyword or pasted search URL, across 59 country storefronts. Every row carries the title, the company, a structured salary, map coordinates, skills, benefits and the full description.

### What you get

One dataset row per job. Export as JSON, CSV or Excel.

```json
{
  "job_id": "a499aa89f203fdc0",
  "title": "Senior Data Engineer",
  "company_name": "Wells Fargo",
  "company_website": "https://www.wellsfargojobs.com/en/well-life/",
  "location": "Iselin, NJ 08830",
  "workplace_type": "hybrid",
  "salary_min": 100000.0,
  "salary_max": 196000.0,
  "salary_currency": "USD",
  "salary_period": "yearly",
  "description_text": "About this role:\n\nWells Fargo is seeking a Senior Data Engineer...",
  "_meta": {"author": "https://apify.com/herus13", "status": "ok", "message": null}
}
```

`_meta` is run metadata, not scraped data. If the run fails or stops early, a row with only `_meta` (status `failed` or `stopped`, the reason in `message`) explains why; those rows are never charged.

Rows also carry `latitude`, `longitude`, `city`, `region` and `postal_code`, so results plot onto a map or filter by true distance instead of by matching a location string. Company enrichment arrives on the same row: `company_website`, `company_twitter`, `company_instagram`, `company_facebook`, `company_industry`, `company_ceo`, `company_hq`, `company_tier` and the rating breakdown behind `company_rating`. Indeed ships skills, benefits, education and certifications as one untyped bag; they come back split into `skills`, `benefits`, `education`, `certifications` and `schedule`, so you can filter on them. `ref_num` is the employer's own requisition ID — the key that joins a listing to an internal system — and `employer_url` is the same job on the company's careers site. Fields Indeed does not publish for a listing come back as `null` rather than invented.

### What it costs

| Event | Free | Bronze | Silver | Gold+ |
|---|---|---|---|---|
| Job scraped (`job-scraped`) | $0.001 | $0.001 | $0.0009 | $0.0008 |

You pay per job, not per minute: $0.001 a job, which is $1.00 per 1,000 on the Free and Bronze tiers, $0.90 per 1,000 on Silver and $0.80 per 1,000 from Gold upward. A 200-job run costs about $0.20 and 10,000 jobs about $10.00. That one per-job price covers the full description in text and HTML and the whole company enrichment set — they are not extra events — and a residential proxy is included in it. There is no start fee.

### Input

| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
| `search_queries` | array | no | — | Keywords to search on Indeed, one per entry. Each keyword returns every matching job up to `max_results`, with title, company, structured salary, location and the full description. Examples: `python developer`, `data scientist`, `product manager`. Provide at least one of `search_queries` or `search_urls`; leave empty to collect only the URLs you paste. |
| `search_urls` | array | no | — | Ready-made Indeed search URLs, one per entry: apply the filters you want on Indeed, then paste the resulting address. Each URL returns every matching job up to `max_results`, and the country comes from the URL itself. Provide at least one of `search_urls` or `search_queries`; leave empty to search by keyword only. |
| `country` | string | no | `"US"` | Indeed country storefront to search. It decides which site is queried, the language of the listings and the currency of every salary. Default US. Supported: US GB CA AU IE DE FR IT NL ES AE AR AT BE BH BR CH CL CO CR CZ DK EC EG FI GR HK HU ID IL IN JP KR KW LU MX MY NG NO NZ OM PA PE PH PK PL PT QA RO SA SE SG TH TR TW UA UY VN ZA. A pasted search URL carries its own country and ignores this setting. |
| `location` | string | no | — | City, state, postcode or `Remote` to narrow the search inside the chosen country. Examples: `New York`, `London`, `Remote`. Leave empty to search the whole country. |
| `job_type` | string | no | — | Employment type a listing must advertise. Values: `full-time`, `part-time`, `contract`, `temporary`, `internship`. Leave empty for any type. Indeed applies the filter before results are returned, so a filtered run does not charge you for rows you would throw away. |
| `workplace_type` | string | no | — | Where the work happens. Values: `remote` fully remote, `hybrid` part office part home, `on-site` at the employer's premises. Leave empty for any arrangement. |
| `date_posted` | string | no | — | How recent a posting must be. Values: `24h` the last day, `3d` the last three days, `week` the last seven days, `2weeks` the last fourteen days. Leave empty for any date. Pair `24h` with a daily schedule to collect only what is new. |
| `salary_min` | integer | no | — | Lowest advertised annual salary a listing must offer, in the currency of the chosen country — for example 80000 in the US. Minimum 0. Leave empty to include every listing, including the many that advertise no salary at all. |
| `fetch_descriptions` | boolean | no | `true` | Also collect each job's full description, in plain text and in HTML, as `description_text` and `description_html`. Default `true`. Set it to `false` for a quicker listing-only run when title, company, salary and location are all you need. |
| `max_results` | integer | no | `200` | Most jobs returned across every keyword and URL combined. Default 200, minimum 1, maximum 10000. Lower it to cap what a run costs; raise it to sweep a busy keyword to the end. |
| `request_delay` | integer | no | `0` | Seconds to wait between requests, to spread a long run out over time. Default 0, minimum 0, which runs at full speed inside the ceiling set by `max_requests_per_minute`. |
| `max_concurrency` | integer | no | `8` | How many requests may be in flight at the same time. Default 8, minimum 1, maximum 20. A higher number finishes a large run sooner; 4 to 10 suits most searches. |
| `max_requests_per_minute` | integer | no | `60` | Ceiling on how many requests a run may send each minute. Default 60, minimum 1, maximum 600. Raise it to finish a wide search sooner, lower it to stretch a long run out. |
| `proxyUrls` | array | no | — | Leave empty: the run uses the residential proxy this actor ships with, included in the price of the run. To route the run through your own provider instead, add one gateway URL per entry, for example `http://user:pass@host:port` — works with any provider that issues URLs. When set, the run uses these first and switches to the included proxy if they stop working, so a broken proxy never fails the run. |

### How to run it

1. Open the actor on Apify Console.
2. Enter one or more search keywords, or paste ready-made Indeed search URLs.
3. Pick the country, add any filters you want, then Run. Export JSON, CSV or Excel from the dataset.

```json
{
  "search_queries": ["python developer"],
  "country": "US",
  "location": "New York",
  "job_type": "full-time",
  "date_posted": "week",
  "fetch_descriptions": true,
  "max_results": 200
}
```

Minimal input:

```json
{
  "search_queries": ["python developer"]
}
```

Available as the MCP tool `herus13--indeed-scraper` on mcp.apify.com and through the Apify API; send the same JSON.

### Why this scraper

- **Richer rows than a search-page scrape** — map coordinates, the employer's requisition ID, the company's own website and socials, industry, size tier and the direct careers-site link all arrive on the same row.
- **Filters cost you nothing** — date posted, job type, workplace type and a salary floor are applied by Indeed before results come back, so you are not billed for rows you would discard.
- **Structured salary** — `salary_min`, `salary_max`, `salary_currency` and `salary_period` parsed out of the raw pay text, with the original string kept as `salary_raw`.
- **59 countries, one input** — the same keyword compared across the US, the UK, Germany, Japan or Australia without running four different tools.
- **Validated output** — mis-read cards (empty title or company, an inverted salary range, an out-of-range rating) are dropped rather than shipped.
- **Nothing to set up** — no account, no cookies, no proxy configuration; a residential proxy is included in the price of the run.

### Use cases

- **ATS and job-board feeds** — A structured salary rather than a free-text string is what makes listings comparable and sortable, and the description in both text and HTML lets the same row serve a parser and a rendered job page.
- **Salary benchmarking by role and market** — 59 storefronts from one input make it practical to compare what a title pays in the US against the UK, Germany or Australia.
- **Competitor hiring intelligence** — What a company is hiring for, and where, is a reliable read on what it is building. Workplace type, posting dates and `is_high_volume_hiring` show whether a team is expanding or backfilling.
- **Recruitment lead generation** — Companies posting heavily in a niche have budget to fill roles, and every row already carries their website, industry and size tier, so a job feed doubles as a lead list.
- **Mapping and radius analysis** — `latitude` and `longitude` on every job mean results plot straight onto a map or filter by real distance.

### FAQ

**How fast is a run?** A 200-job run with full descriptions finishes in a few minutes on the 512 MB default. Raise `max_requests_per_minute` and `max_concurrency` to go faster, or lower them to stretch a long run out.

**Which countries are supported?** 59 storefronts: United States (US), United Kingdom (GB), Canada (CA), Australia (AU), Ireland (IE), Germany (DE), France (FR), Italy (IT), Netherlands (NL), Spain (ES), United Arab Emirates (AE), Argentina (AR), Austria (AT), Belgium (BE), Bahrain (BH), Brazil (BR), Switzerland (CH), Chile (CL), Colombia (CO), Costa Rica (CR), Czechia (CZ), Denmark (DK), Ecuador (EC), Egypt (EG), Finland (FI), Greece (GR), Hong Kong (HK), Hungary (HU), Indonesia (ID), Israel (IL), India (IN), Japan (JP), South Korea (KR), Kuwait (KW), Luxembourg (LU), Mexico (MX), Malaysia (MY), Nigeria (NG), Norway (NO), New Zealand (NZ), Oman (OM), Panama (PA), Peru (PE), Philippines (PH), Pakistan (PK), Poland (PL), Portugal (PT), Qatar (QA), Romania (RO), Saudi Arabia (SA), Sweden (SE), Singapore (SG), Thailand (TH), Turkey (TR), Taiwan (TW), Ukraine (UA), Uruguay (UY), Vietnam (VN), South Africa (ZA).

**Can I just paste an Indeed search URL?** Yes. Apply the filters you want on Indeed and paste the resulting address into `search_urls`. The country comes from the URL itself, so you do not need to set `country` as well.

**Why are some fields empty?** They are simply not on every listing: roughly a fifth of jobs carry no employment-type chip, and not every employer keeps a company profile. Missing fields come back as `null` rather than fabricated.

**Is a proxy included?** A residential proxy is included in the price of the run. To use your own, paste gateway URLs into **Your own proxy URLs**.

**What happens if my input is wrong?** The run stops before it scrapes anything, so no results are charged. Its status and log name each field that is wrong and say what to change — fix the input and run again.

**Is scraping Indeed legal?** This actor collects only publicly available job-listing data. You are responsible for complying with Indeed's terms and applicable laws. Do not collect personal data without a lawful basis.

### Related actors

Building a job-market pipeline? Pair this actor with:

- [LinkedIn Jobs Scraper](https://apify.com/herus13/linkedin-jobs-scraper) — LinkedIn job listings with company detail, salary and applicant counts.
- [JobStreet & JobsDB Scraper](https://apify.com/herus13/jobstreet-scraper) — job listings across Malaysia, Singapore, the Philippines, Indonesia, Thailand and Hong Kong.
- [levels.fyi Salary Scraper](https://apify.com/herus13/levels-fyi-salary-scraper) — verified tech compensation by company, level and location.
- [LinkedIn Profile Scraper](https://apify.com/herus13/linkedin-profile-scraper) — LinkedIn profiles for sourcing and recruitment lead generation.

# Actor input Schema

## `search_queries` (type: `array`):

Keywords to search on Indeed, one per entry. Each keyword returns every matching job up to `max_results`, with title, company, structured salary, location and the full description. Examples: `python developer`, `data scientist`, `product manager`. Provide at least one of `search_queries` or `search_urls`; leave empty to collect only the URLs you paste.

## `search_urls` (type: `array`):

Ready-made Indeed search URLs, one per entry: apply the filters you want on Indeed, then paste the resulting address. Each URL returns every matching job up to `max_results`, and the country comes from the URL itself. Provide at least one of `search_urls` or `search_queries`; leave empty to search by keyword only.

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

Indeed country storefront to search. It decides which site is queried, the language of the listings and the currency of every salary. Default US. Supported: US GB CA AU IE DE FR IT NL ES AE AR AT BE BH BR CH CL CO CR CZ DK EC EG FI GR HK HU ID IL IN JP KR KW LU MX MY NG NO NZ OM PA PE PH PK PL PT QA RO SA SE SG TH TR TW UA UY VN ZA. A pasted search URL carries its own country and ignores this setting.

## `location` (type: `string`):

City, state, postcode or `Remote` to narrow the search inside the chosen country. Examples: `New York`, `London`, `Remote`. Leave empty to search the whole country.

## `job_type` (type: `string`):

Employment type a listing must advertise. Values: `full-time`, `part-time`, `contract`, `temporary`, `internship`. Leave empty for any type. Indeed applies the filter before results are returned, so a filtered run does not charge you for rows you would throw away.

## `workplace_type` (type: `string`):

Where the work happens. Values: `remote` fully remote, `hybrid` part office part home, `on-site` at the employer's premises. Leave empty for any arrangement.

## `date_posted` (type: `string`):

How recent a posting must be. Values: `24h` the last day, `3d` the last three days, `week` the last seven days, `2weeks` the last fourteen days. Leave empty for any date. Pair `24h` with a daily schedule to collect only what is new.

## `salary_min` (type: `integer`):

Lowest advertised annual salary a listing must offer, in the currency of the chosen country — for example 80000 in the US. Minimum 0. Leave empty to include every listing, including the many that advertise no salary at all.

## `fetch_descriptions` (type: `boolean`):

Also collect each job's full description, in plain text and in HTML, as `description_text` and `description_html`. Default `true`. Set it to `false` for a quicker listing-only run when title, company, salary and location are all you need.

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

Most jobs returned across every keyword and URL combined. Default 200, minimum 1, maximum 10000. Lower it to cap what a run costs; raise it to sweep a busy keyword to the end.

## `request_delay` (type: `integer`):

Seconds to wait between requests, to spread a long run out over time. Default 0, minimum 0, which runs at full speed inside the ceiling set by `max_requests_per_minute`.

## `max_concurrency` (type: `integer`):

How many requests may be in flight at the same time. Default 8, minimum 1, maximum 20. A higher number finishes a large run sooner; 4 to 10 suits most searches.

## `max_requests_per_minute` (type: `integer`):

Ceiling on how many requests a run may send each minute. Default 60, minimum 1, maximum 600. Raise it to finish a wide search sooner, lower it to stretch a long run out.

## `proxyUrls` (type: `array`):

Leave empty: the run uses the residential proxy this actor ships with, included in the price of the run. To route the run through your own provider instead, add one gateway URL per entry, for example `http://user:pass@host:port` — works with any provider that issues URLs. When set, the run uses these first and switches to the included proxy if they stop working, so a broken proxy never fails the run.

## Actor input object example

```json
{
  "search_queries": [
    "python developer"
  ],
  "country": "US",
  "fetch_descriptions": true,
  "max_results": 200,
  "request_delay": 0,
  "max_concurrency": 8,
  "max_requests_per_minute": 60
}
```

# Actor output Schema

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

List of Indeed job listings with title, company, structured salary, location, full description, posting date, geo coordinates, company enrichment (website, socials, industry, CEO, size tier), employer requisition ID, direct employer job URL, occupation taxonomy and hiring-demand flags. Geo and company enrichment fields are populated in API search mode only. Each row also carries `_meta` (author, status, message): run metadata, not scraped data.

# 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 = {
    "search_queries": [
        "python developer"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("herus13/indeed-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 = { "search_queries": ["python developer"] }

# Run the Actor and wait for it to finish
run = client.actor("herus13/indeed-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 '{
  "search_queries": [
    "python developer"
  ]
}' |
apify call herus13/indeed-scraper --silent --output-dataset

```

## MCP server setup

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