# Indeed Scraper (`mina_safwat/indeed-scraper`) Actor

Scrapes Indeed job postings with salaries — titles, companies, locations, pay ranges, and full descriptions, across 70+ countries

- **URL**: https://apify.com/mina\_safwat/indeed-scraper.md
- **Developed by:** [Mina](https://apify.com/mina_safwat) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 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/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

Scrape **Indeed job postings with salaries**: titles, companies, locations, pay ranges, and full descriptions, across 70+ countries.

### What does Indeed Scraper do?

Give it a job title and a location and it returns matching [Indeed](https://www.indeed.com) postings as structured data: the job title, employer, location, whether it is remote, employment type, posting date, **salary range**, the Indeed link and the employer's own apply link, a short employer profile, and the full job description.

It is built for Indeed alone. Indeed answers automated searches far more reliably than other boards, and it publishes pay ranges on a large share of its listings, so a dedicated Actor can lean on both instead of settling for the lowest common denominator across several sites.

Running it on Apify adds API access, scheduling, integrations (Google Sheets, Slack, Zapier, S3), residential proxy rotation, and run monitoring.

### Why use Indeed Scraper?

- **Salary benchmarking**: collect hundreds of advertised pay ranges for a role and market.
- **Recruitment research**: see who is hiring, where, and what they are offering.
- **Competitor hiring intelligence**: track a rival's openings over time and spot where they are growing.
- **Job aggregation**: feed a niche job board or a candidate newsletter.
- **Job hunting at scale**: pull every relevant opening into one spreadsheet.

### How to scrape Indeed jobs

1. Enter one or more **Job titles or keywords**, e.g. `data engineer`.
2. Enter a **Location** and pick the matching **Country**.
3. Set **Results per search term** and click **Start**.

Results appear in the Output tab as they are scraped.

### Input

Set the input on the Input tab, or send it as JSON through the API. Only `search_terms` is required.

| Field | Description |
| --- | --- |
| `search_terms` | Job titles or keywords, one per line. Each is searched separately and results are merged with duplicates removed. |
| `location` | City, state, or region. Leave empty to search the whole country. |
| `country` | Which Indeed site to search. Indeed runs a separate site per country. |
| `results_wanted` | How many jobs to collect per keyword (default 50). |
| `hours_old` | Only recent postings: 24 for today, 168 for the past week. |
| `job_type` | Full-time, part-time, contract, or internship. |
| `remote_only` | Only jobs advertised as remote. |
| `distance_miles` | How far from the location to search. |
| `easy_apply_only` | Only jobs you can apply to on Indeed itself. |
| `annual_salary_only` | Convert hourly and monthly pay to a yearly figure so salaries compare directly. |
| `offset` | Skip the first N results, for paging across runs. |
| `proxy_country` | Comma-separated 2-letter country codes for the residential proxy exits (default `US,GB,DE,NL,FR`). `UK` is read as `GB`. Malformed codes are skipped, and the run stops with a message if none is usable. |

**Indeed applies one filter group per search.** "Posted within" (`hours_old`) wins over Easy Apply, and Easy Apply wins over job type and remote. Lower-priority filters are ignored, and the run log says which ones were dropped. To combine them, run one filter per search.

```json
{
  "search_terms": ["data engineer"],
  "location": "New York, NY",
  "country": "USA",
  "results_wanted": 50
}
```

### Output

```json
{
  "job_id": "in-1229f7f602870447",
  "title": "Senior Data Engineer",
  "company": "Acme Corp",
  "location": "New York, NY, US",
  "is_remote": false,
  "job_type": "fulltime",
  "date_posted": "2026-09-30",
  "salary_min": 105000,
  "salary_max": 215000,
  "salary_currency": "USD",
  "salary_interval": "yearly",
  "salary_source": "direct_data",
  "description": "Do you enjoy solving complex problems in a fast-paced team? ...",
  "job_url": "https://www.indeed.com/viewjob?jk=1229f7f602870447",
  "apply_url": "https://careers.acme.com/job/101361028304",
  "company_url": "https://www.indeed.com/cmp/Acme-Corp",
  "company_industry": "Consumer Goods And Services",
  "company_size": "10,000+",
  "company_logo": "https://d2q79iu7y748jz.cloudfront.net/s/_squarelogo/256x256/9cc561e7",
  "company_address": "New York, NY",
  "emails": ["careers@acme.com"]
}
```

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

#### Data fields

| Field | Description |
| --- | --- |
| `job_id`, `title`, `company`, `location` | The posting basics. |
| `is_remote` | Whether the job is advertised as remote. |
| `job_type` | Employment type (e.g. `fulltime`, `parttime`, `contract`), when the posting states one. |
| `date_posted` | When the job was published. |
| `salary_min`, `salary_max`, `salary_currency`, `salary_interval` | Advertised pay, and whether it is hourly, daily, weekly, monthly, or yearly. Empty when the posting has no pay. |
| `salary_source` | `direct_data`: the pay comes from Indeed's own structured salary data, not guessed from the job text. |
| `description` | Full job description. Headings, lists, and bold text are kept as Markdown, and escape backslashes are removed so it also reads cleanly as plain text. |
| `job_url` | The Indeed posting. |
| `apply_url` | The employer's own application link, where Indeed exposes it. |
| `company_url` | The employer's Indeed company page. |
| `company_size`, `company_logo`, `company_address` | Employer profile from Indeed, filled for most employers. `company_address` is the headquarters address. |
| `company_industry` | The employer's industry. Indeed lists it for only a minority of employers, so it is often empty. |
| `emails` | A list of contact addresses that appear in the description. It is empty for most postings. |

### How much does it cost to scrape Indeed?

The Actor is priced per result. You pay **$0.002 per job** saved to the dataset, plus **$0.00005 per run start** (charged once per GB of memory, and the default 1 GB is all the Actor needs). That works out to **about $2.00 per 1,000 results**. Proxy and compute costs are included. Searches that return nothing are not billed, because they are never saved as rows.

**Results per search term** is the main cost lever. A run with 3 keywords at 50 results each costs at most about $0.30. The Apify free plan's monthly credit covers roughly 2,500 jobs.

### Tips for scraping Indeed

- **Daily monitoring.** Schedule the Actor with `hours_old: 24` and each run returns only what is new.
- **Several narrow searches beat one broad one.** Indeed limits how deep any single search goes, so `senior python engineer` plus `python engineer` returns more than `engineer` alone.
- **Turn on annual conversion** when you compare pay across roles. Otherwise hourly and yearly figures sit in the same column.
- **Country is a separate site.** Searching `USA` will not return UK listings. Run each market you care about.
- **Check the run's status message.** Search terms that returned nothing, or that Indeed cut off partway, are listed with a reason under `FAILED_INPUTS` in the run's key-value store. The run fails only when Indeed refused every search.

### FAQ, disclaimers, and support

**Why do some jobs have no salary?** Many employers simply do not publish pay. The Actor only reports pay that Indeed itself attaches to the posting. It does not guess from the description, because many postings list different ranges for several cities, and a guess would often pick the wrong one.

**Why did I get fewer results than I asked for?** Indeed caps how far any single search goes. When a search is exhausted the Actor stops rather than re-collecting the same postings. To get more, split the search into narrower terms or locations.

**Does it need an Indeed account or API key?** No. It reads only publicly visible job postings.

**Is scraping Indeed legal?** The Actor collects only publicly visible job postings, not candidate data. Indeed's Terms of Service restrict automated access, and you are responsible for how you use the data. Some postings include recruiter names or email addresses, which can be personal data under GDPR and similar laws, so only store them if you have a lawful reason. Consult a lawyer if you are unsure.

Found a bug or want a field that is missing? Open an issue on the Actor's Issues tab. Custom solutions are available on request.

# Actor input Schema

## `search_terms` (type: `array`):

What to search for, one per line. Each term is searched separately and the results are merged with duplicates removed.

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

City, state, or region — e.g. "New York, NY" or "London". Leave empty to search the whole country.

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

Which Indeed site to search. Indeed runs a separate site per country and results differ between them.

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

How many jobs to collect for each keyword. Indeed limits how deep any one search goes, so very large numbers may return fewer.

## `hours_old` (type: `integer`):

Only jobs posted in the last N hours. 24 for today, 168 for the past week. Leave empty for any age. When set, Indeed ignores Easy Apply, job type and remote filters.

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

Restrict to one type of employment. Leave empty for all. Ignored when "Posted within" or Easy Apply is set.

## `remote_only` (type: `boolean`):

Only jobs advertised as remote. Ignored when "Posted within" or Easy Apply is set.

## `distance_miles` (type: `integer`):

How far from the location to search. Leave empty for Indeed’s default.

## `easy_apply_only` (type: `boolean`):

Only jobs you can apply to directly on Indeed, without being sent to the employer’s own site. Ignored when "Posted within" is set; when set, Indeed ignores job type and remote filters.

## `annual_salary_only` (type: `boolean`):

Convert hourly and monthly pay to a yearly figure so every salary compares directly.

## `offset` (type: `integer`):

Start further down the result list — useful for paging through a large search across several runs.

## `proxy_country` (type: `string`):

Comma-separated 2-letter codes, e.g. US,GB,DE. Indeed blocks datacentre addresses, so residential proxies are recommended.

## Actor input object example

```json
{
  "search_terms": [
    "data engineer"
  ],
  "location": "New York, NY",
  "country": "USA",
  "results_wanted": 50,
  "remote_only": false,
  "easy_apply_only": false,
  "annual_salary_only": false,
  "offset": 0,
  "proxy_country": "US,GB,DE,NL,FR"
}
```

# Actor output Schema

## `dataset` (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 = {
    "search_terms": [
        "data engineer"
    ],
    "location": "New York, NY"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mina_safwat/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_terms": ["data engineer"],
    "location": "New York, NY",
}

# Run the Actor and wait for it to finish
run = client.actor("mina_safwat/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_terms": [
    "data engineer"
  ],
  "location": "New York, NY"
}' |
apify call mina_safwat/indeed-scraper --silent --output-dataset

```

## MCP server setup

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