# Indeed Jobs Scraper - Full Descriptions, Salaries & Ratings (`webdatalabs/indeed-jobs-scraper`) Actor

Scrape Indeed job postings by keyword and location. Returns title, company, company rating, location, remote type, salary (raw + normalized), job type, posted date, the FULL job description and an apply URL. Optional AI enrichment adds seniority, skills and a normalized title. Null-honest output.

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

## Pricing

from $4.20 / 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 Jobs Scraper - Full Descriptions, Salaries & Ratings

Search Indeed the way you would in a browser - a job title and a location - and get back a clean
row per posting with the **complete job description**, the advertised salary as both raw text and
normalized numbers, the employer's Indeed rating, and a working apply link.

Fields Indeed does not state come back as `null`. Never `0`, never `"N/A"`, never a guess.

### What data can I extract from Indeed?

| Field | Example |
|---|---|
| `title` | `Software Engineer, Early Career, Campus` |
| `company` | `Google` |
| `companyRating` / `companyReviewCount` | `4.3` / `6268` |
| `location` / `city` / `state` | `New York, NY` / `New York` / `NY` |
| `isRemote` / `remoteType` | `false` / `null` (or `remote`, `hybrid`, `onsite`) |
| `postedAt` / `postedAgoLabel` | `2026-08-07T05:00:00.000Z` / `30+ days ago` |
| `salaryDisclosed` | `true` |
| `salaryMin` / `salaryMax` | `123000` / `174000` |
| `salaryCurrency` / `salaryPeriod` | `USD` / `year` |
| `salaryRaw` | `$123,000 - $174,000 a year` |
| `jobType` | `Full-time` |
| `description` | the full posting text, emails and phone numbers removed |
| `applyUrl` / `jobUrl` | clean links, no expiring tracking tokens |
| `aiSeniority` / `aiNormalizedTitle` / `aiSkills` | with AI enrichment on |

`postedAt` is Indeed's own timestamp, not a guess reconstructed from "30+ days ago".

### Example output

A real record, unedited except for truncating the description:

```json
{
  "jobId": "e6489bd83820f079",
  "title": "Software Engineer, Early Career, Campus",
  "company": "Google",
  "companyRating": 4.3,
  "companyReviewCount": 6268,
  "location": "New York, NY",
  "city": "New York",
  "state": "NY",
  "isRemote": false,
  "remoteType": null,
  "postedAt": "2026-08-07T05:00:00.000Z",
  "postedAgoLabel": "30+ days ago",
  "salaryDisclosed": true,
  "salaryMin": 123000,
  "salaryMax": 174000,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salaryRaw": "$123,000 - $174,000 a year",
  "jobType": "Full-time",
  "description": "The application window will be open until at least September 30, 2026 …",
  "applyUrl": "https://www.indeed.com/applystart?jk=e6489bd83820f079",
  "jobUrl": "https://www.indeed.com/viewjob?jk=e6489bd83820f079",
  "aiSeniority": null,
  "aiNormalizedTitle": null,
  "aiSkills": null,
  "query": "software engineer",
  "searchLocation": "New York, NY",
  "country": "us",
  "scrapedAt": "2026-09-22T17:10:37.742Z"
}
```

When an employer does not advertise pay, the honest version comes back instead - `salaryDisclosed:
false` with every salary field `null`, so you can filter on "actually disclosed" rather than
guessing which zeros are real.

### How to use the Indeed scraper

1. Put a job title or keyword in **Job title or keyword** - whatever you would type into Indeed.
2. Add a **Location** (`New York, NY`, `London`, `Berlin`). Leave it empty to search the country.
3. Pick the **Country** - this chooses the Indeed site, and with it the listing language.
4. Set **Maximum jobs**. The actor keeps searching until it reaches that number or runs out of new
   postings.

Everything else has a sensible default.

### How many jobs can I get from one search?

This is the honest limit and it is worth understanding before you run a large job.

**Indeed serves logged-out visitors exactly one page of results per search.** Page two redirects to
a sign-in wall, on every country site. No scraper can page past it without an account.

So this actor goes deeper the way a person would: it **repeats your search with different filters** -
date windows, job type, radius, experience level, sorted by date instead of relevance - and merges
the results, removing duplicates. Every repeat is a fresh first page with a partly different set of
postings.

What that means in practice:

- A single search returns roughly **40+ jobs on Indeed US**, and about **15 on other country sites**.
- Repeating with filters reliably multiplies that. A measured run on `software engineer` in
  `New York, NY` collected **183 distinct jobs from 16 searches**.
- Returns diminish as it goes. Each extra search finds fewer postings the run has not already seen,
  so the actor stops once a search stops paying for itself. **Stop when a search adds fewer new jobs
  than this** (Advanced) controls where that line sits; set it to `0` to exhaust every filter.
- The most effective way to go deeper is **more locations**, not a bigger `maxJobs` - run the
  neighbouring cities as separate runs rather than asking one search for thousands of rows.

A run that stops early tells you why in the log, and you are only ever charged for jobs actually
delivered.

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

Pay per result, at the rates shown in the pricing box above - one charge per job delivered, and a
separate smaller charge per job when AI enrichment is switched on. There is no subscription, and a
trial run of a hundred jobs costs a few cents.

You are charged for jobs that reach your dataset. A run that is cut short by its budget, its
timeout, or by Indeed running out of matches bills only for what it handed over.

### Who is this Indeed scraper for?

- **Recruiters and staffing agencies** tracking who is hiring, for what, and at what pay.
- **Compensation and HR teams** benchmarking salaries with `salaryMin`/`salaryMax` already parsed.
- **Job boards and aggregators** that need full descriptions, not truncated snippets.
- **Market researchers** measuring hiring demand by role, company or city over time.
- **AI and RAG pipelines** - the description is clean plain text with contact details removed.

### Which countries are supported?

Eighteen Indeed sites, each verified: United States, United Kingdom, Canada, Australia, India,
Ireland, Singapore, Germany, Austria, Switzerland, France, Netherlands, Spain, Italy, Poland,
Brazil, Mexico and Japan.

### Frequently asked questions

#### Is it legal to scrape Indeed?

This actor reads only public job postings that Indeed serves to logged-out visitors. It does not log
in, does not touch personal profiles, and does not defeat any security measure - Indeed's bot
protection is handled by Apify's own Web Unblocker proxy. Job postings are business information
rather than personal data, and email addresses and phone numbers are stripped out of descriptions
before anything is written, since recruiter contact details do sometimes appear there. You are
responsible for how you use the data, including under GDPR where it applies.

#### Does it really include the full job description?

Yes - the complete posting text for every job, not the two-line snippet Indeed shows on the results
page. In the verification run every one of 50 jobs came back with a description, ranging from about
900 to 10,000 characters.

#### Why does one run make several searches?

Because Indeed only gives logged-out visitors one page per search. See "How many jobs can I get"
above - the repeated searches are how the actor gets past that, and they are what your `maxJobs`
target is spent on.

#### What happens when a job does not show a salary?

`salaryDisclosed` is `false` and every salary field is `null`. Most postings do not advertise pay,
and a fabricated `0` would be indistinguishable from a real one once it is in your spreadsheet.

#### Do I need to configure a proxy?

No. The default is Apify's Web Unblocker, which is what handles Indeed's Cloudflare challenge.
Residential and datacenter proxies were measured failing against Indeed - residential succeeds and
fails in waves on the same URL - so change this only if you know why you are changing it.

#### What does AI enrichment add?

Three extra fields per job, read out of the full description: `aiSeniority` (Intern through
Director+), `aiNormalizedTitle` (the role reduced to a standard name so you can group across
employers), and `aiSkills` (the technologies and skills the posting names). It is off by default and
billed separately, per enriched job. When the model cannot support an answer the field is `null`
rather than a guess.

#### Can I get only recent jobs?

Yes - **Posted within** applies Indeed's own date filter, so the search itself goes deeper into
recent postings rather than filtering afterwards.

### Related actors

- [Google Jobs Scraper](https://apify.com/webdatalabs/google-jobs-intelligence) - the same shape for
  the Google Jobs vertical, which aggregates LinkedIn, Stepstone, XING and company career sites.
- [Naukri Jobs Scraper](https://apify.com/webdatalabs/naukri-jobs-pro) - India, with salary bands and
  skills.

### Support

Something wrong or missing? Open an issue on the actor's Issues tab with the run ID and the input you
used, and it will get looked at.

### Leave a review

If this saved you time, a review on the Apify Store genuinely helps other people find it.

# Actor input Schema

## `query` (type: `string`):

What to search for on Indeed, exactly as you would type it into the search box - for example "software engineer", "registered nurse" or "warehouse".

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

City, state or postcode to search in, as Indeed writes it - for example "New York, NY", "London" or "Berlin". Leave empty to search the whole country.

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

Which Indeed site to search. This picks the country domain, so it also decides the language of the listings and the salary currency.

## `maxJobs` (type: `integer`):

Stop after this many jobs. The actor searches progressively deeper to reach the number you ask for, and stops early when Indeed stops returning new postings.

## `datePosted` (type: `string`):

Only return jobs posted inside this window. Applied by Indeed itself, so it also makes the search go deeper into recent postings.

## `includeAiEnrichment` (type: `boolean`):

Adds seniority, a normalized job title and the skills mentioned in the description, inferred from the full text by AI. Billed per enriched job on top of the per-job rate.

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

Leave this alone unless you know you need to change it. Indeed is behind a Cloudflare challenge that only Apify's Web Unblocker clears reliably - residential proxies were measured succeeding and failing in waves on the same URL, and datacenter proxies are blocked outright.

## `minNewJobsPerRequest` (type: `integer`):

Indeed shows only one page of results per search, so the actor reaches your target by repeating the search with different filters. Each repeat returns fewer jobs it has not already seen. This is the point at which it stops paying for more searches.

## `debug` (type: `boolean`):

Log every search, its yield and the field extraction rates. Useful when the output looks wrong.

## Actor input object example

```json
{
  "query": "software engineer",
  "location": "New York, NY",
  "country": "us",
  "maxJobs": 100,
  "datePosted": "any",
  "includeAiEnrichment": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "UNBLOCKER"
    ]
  },
  "minNewJobsPerRequest": 3,
  "debug": false
}
```

# Actor output Schema

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

The role, who is hiring, what it pays and where to apply.

## `full` (type: `string`):

Every field, including the complete job description, the normalized salary figures and the AI fields.

# 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 = {
    "query": "software engineer",
    "location": "New York, NY",
    "maxJobs": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("webdatalabs/indeed-jobs-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 = {
    "query": "software engineer",
    "location": "New York, NY",
    "maxJobs": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("webdatalabs/indeed-jobs-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 '{
  "query": "software engineer",
  "location": "New York, NY",
  "maxJobs": 100
}' |
apify call webdatalabs/indeed-jobs-scraper --silent --output-dataset

```

## MCP server setup

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