# Indeed Jobs Scraper: Salaries, Companies & New Job Alerts (`moggedhq/indeed-jobs-scraper`) Actor

Get Indeed jobs for software engineers, nurses or sales teams by keyword and city, with salaries, company ratings, posting dates and full descriptions. Run daily new-job alerts with saved watch history. About $0.002 per job: 1,000 jobs cost $2. No login.

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

## Pricing

from $1.60 / 1,000 jobs

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: Salaries, Companies & New Job Alerts

For recruiting teams, job market analysts and people building a daily hiring monitor. Search Indeed by job title and city, export disclosed salaries and company ratings, and keep a daily watch that delivers only new matching job IDs.

A Job costs **$0.002**, including its public description when requested. **1,000 delivered jobs cost $2**, plus a $0.00005 start fee at the standard 512 MB setting. Bronze, Silver and Gold discounts are 5%, 10% and 20%. You do not need an Indeed account, exported cookies or a separate data service.

### What you get

| Data | Fields and meaning |
| --- | --- |
| Job | `jobId`, `title`, `company`, `url`, `employmentType` |
| Salary | `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryText`, `salaryDisclosed` |
| Company | `companyRating`, `companyReviewCount`, `companyUrl`, `companyWebsite`, `companyLogo`, `companySize`, `industry` |
| Location | `locations`, `city`, `state`, `postalCode`, `country`, `latitude`, `longitude` |
| Dates | `postedAt` for first appearance on Indeed, `datePublished` for the supplied publication date, `scrapedAt` for this observation |
| Work arrangement | `workMode` when an explicit remote, hybrid or office source tag exists |
| Description | `description`, `detailsIncluded`, and explicit experience or education text when requested |
| Source attributes | `attributes`, an array of Indeed taxonomy keys and labels |
| Monitoring | `isNewSinceLastRun`, plus a saved history when daily watch mode is enabled |
| Run overview | A free `SUMMARY` row and a `SUMMARY` storage record with delivered jobs, company totals, salary spread and exclusions |

Unavailable information stays `null`. Applicant counts, private employer data and candidate records are not extracted.

### Start in three steps

1. Enter `keywords`, for example `software engineer`, `nurse` or `accountant`.
2. Enter one or more `locations`, choose the matching `country`, and set `maxItems`.
3. Start the run and open the Dataset. Use the overview for salaries and the details view for descriptions, then export JSON, CSV or Excel.

The default asks for 20 software engineering jobs around New York. A cloud validation run on 29 October 2026 delivered 20 jobs in 7.2 seconds. A separate 100-job nursing run with descriptions finished in 23 seconds. These are measured examples; source response times and available results vary.

```json
{
  "keywords": ["software engineer"],
  "locations": ["New York, NY"],
  "country": "US",
  "maxItems": 20,
  "includeDetails": true
}
```

Keywords are searched across each supplied location. Company names create separate targeted searches; full names work better than short fragments. The global cap can fill before later cities are reached.

### Inputs

| Input | Default | How to use it |
| --- | --- | --- |
| `keywords` | Software engineer when no search or URL is supplied | One or more job titles, skills or Indeed search expressions |
| `locations` | A major city for US, GB, CA, AU and ZA; otherwise nationwide | Cities, regions or postal codes in the chosen market |
| `country` | `US` | `US`, `GB`, `CA`, `AU`, `NZ`, `ZA`, `IN`, `IE`, `DE` or `FR` |
| `radius` | `25` | From 0 to 100; miles for US and GB, kilometres elsewhere |
| `maxItems` | `20` | From 1 to 5,000 unique delivered jobs across the whole run |
| `includeDetails` | `false` | Add the public job description, with contact lines removed; no separate description fee |
| `companies` | No filter | Names for Indeed company searches, then case-insensitive employer-name verification |
| `workMode` | `any` | `remote`, `hybrid` or `office` requires an explicit matching source tag |
| `postedWithinDays` | No filter | Compare the exact `postedAt` timestamp to the run start time |
| `salaryMin` | No filter | Minimum explicitly disclosed annual lower bound in the market's currency |
| `experienceYears` | No filter | Strict match against an explicit experience range in the description |
| `startUrls` | Empty | Optional Indeed search URLs or individual job links |
| `onlyNewSinceLastRun` | `false` | Enable persistent new-job monitoring |
| `watchId` | `default` | Stable name of a watch; 1 to 80 letters, digits, underscores or hyphens |
| `proxyConfiguration` | Automatic | Datacenter first; residential retries only after a refusal or transport failure |

The runtime also accepts `query` or `searchTerms` for keywords, `location` for locations and `urls` for start URLs.

#### Salary research

```json
{
  "keywords": ["software engineer"],
  "locations": ["Seattle, WA"],
  "country": "US",
  "salaryMin": 80000,
  "maxItems": 50
}
```

This keeps jobs with a disclosed annual lower bound of at least $80,000. It excludes hourly pay and jobs with no disclosed lower bound. It does not turn an hourly figure into an annual estimate, compare different currencies or substitute an Indeed estimated salary.

A range, an exact amount, a minimum-only amount and a maximum-only amount retain their different meanings. The absent bound stays null. Salary spread in the summary is grouped by currency and pay period, so hourly and annual figures are never combined into one misleading range.

#### Read known jobs or saved searches

```json
{
  "startUrls": [
    "https://www.indeed.com/jobs?q=nurse&l=Boston%2C+MA&fromage=7",
    "https://www.indeed.com/viewjob?jk=d352556bfc9f8ca7"
  ],
  "maxItems": 30,
  "includeDetails": true
}
```

Direct job links are checked before keyword searches. Job IDs deduplicate across links, searches and pages. A job removed or expired at the source produces a free note rather than an invented job row.

Imported search URLs honor `q`, `l`, `radius` and `fromage`. This Actor uses its own date-oriented source request and begins at the first page; imported `sort` and `start` do not select a different order or initial page. Other filters, including opaque `sc` expressions, are rejected with a free validation note. Recreate supported filters using the documented inputs. Company profile URLs and browsing categories are not supported URL inputs.

### Monitor new jobs every day

Create a task using this input and attach an Apify daily schedule to that task:

```json
{
  "keywords": ["accountant"],
  "locations": ["Chicago, IL"],
  "country": "US",
  "maxItems": 100,
  "onlyNewSinceLastRun": true,
  "watchId": "chicago-accounting"
}
```

The first run establishes a baseline and delivers up to the cap. Later runs require both an unseen ID and a source `dateOnIndeed` timestamp after the last completed watch window. Older jobs discovered on a later page do not suddenly become new alerts. Changes to an existing job ID are not emitted as new jobs.

A later run that fills its cap keeps its old watch timestamp. Run the task again to drain the remaining new-job backlog. An uncapped successful completion advances the window. Failed source reads and spending-limit stops keep the old window. The baseline intentionally treats older uncollected jobs as history when its window advances.

Watch history is scoped to the Actor, your account and `watchId`. Use separate names for different searches, and keep a watch's criteria stable. A lock prevents overlapping runs from delivering the same watch simultaneously. If a run is interrupted, resume it before starting another run with the same watch name; an unfinished marker preserves that continuity. Changing the name creates an independent baseline. Deleting a task does not reset its named watch storage.

### Output example

These selected fields came from a real signed-out cloud run on 29 October 2026:

```json
{
  "jobId": "d352556bfc9f8ca7",
  "title": "Junior Software Engineer",
  "company": "SOCOTEC",
  "locations": ["New York, NY"],
  "workMode": "hybrid",
  "salaryMin": 110000,
  "salaryMax": 140000,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salaryDisclosed": true,
  "companyRating": 3,
  "companyReviewCount": 605,
  "postedAt": "2026-08-03T18:20:32.227Z",
  "url": "https://www.indeed.com/viewjob?jk=d352556bfc9f8ca7"
}
```

`verdict` is a readable summary at the start of each job row. `NOTE` and `SUMMARY` records are distinguishable by `recordType`; useful job rows have `jobId`. Filter on `jobId` when a downstream integration should accept jobs only.

The shared job schema also contains conservative compatibility fields such as `companyId`, `skills`, `department`, `roleCategory`, `applicants` and `applyUrl`. They remain null when this route does not supply a verified value. Use the explicit `attributes` labels for taxonomy analysis; do not assume every label is an employer-required skill. `industry` can be a native source classification code.

### Pricing and spending limits

| Delivered jobs | Standard Job charges |
| --- | --- |
| 20 | $0.04 |
| 100 | $0.20 |
| 1,000 | $2 |

Add the small start fee. Discounts apply to Job charges, and descriptions are included. A repeat watch with no new jobs has no Job charge. Failed reads, rejected inputs, duplicate IDs, excluded jobs and summaries have no Job charge.  The Actor pays platform usage under its configured pay-per-event model.

The Actor checks available room before saving each new job, stops cleanly when the next Job is unaffordable and records the stop in the summary. A delivered job is saved before its charge. Stable charge keys, a durable dataset ledger and acknowledged charge receipts support recovery after a reboot without charging a saved job twice.

### Use with AI agents (MCP)

Connect an Apify-compatible agent to `https://mcp.apify.com`. Ask it to use `moggedhq/indeed-jobs-scraper` with a small input first. For example: find 20 nursing jobs around Boston, include public descriptions, and return title, salary, company rating and posting date. Supply `keywords`, `locations`, `country` and `maxItems` directly. For a daily monitor, also supply a persistent `watchId` and `onlyNewSinceLastRun: true`.

It should preserve null salary bounds and pay periods when comparing results. This Actor does not apply to jobs or send alerts itself; connect the output to your own notification workflow.

### API and integrations

JavaScript using the Apify client:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('moggedhq/indeed-jobs-scraper').call({
  keywords: ['nurse'], locations: ['Boston, MA'], maxItems: 20,
  includeDetails: true
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const jobs = items.filter(item => item.jobId);
```

Python uses the same input:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('moggedhq/indeed-jobs-scraper').call(run_input={
    'keywords': ['accountant'], 'locations': ['Chicago, IL'],
    'maxItems': 20
})
jobs = [item for item in client.dataset(run['defaultDatasetId']).iterate_items()
        if item.get('jobId')]
```

For Make, Zapier or n8n, start the Actor or a saved task, wait for completion, read the dataset and select rows containing `jobId`. Send those rows to a spreadsheet, database or notification step. An Apify schedule can start the daily task; a success webhook can trigger the next step. A successful run can still contain source notes, so inspect `SUMMARY.status` and `freeFailures` as well as the run status.

### Limits and practical questions

**Does it log into Indeed?** No. It reads Indeed's signed-out native job data. It does not request account cookies, solve challenges or access candidate records.

**Does it return every matching job?** No completeness guarantee is made. Each search reads at most ten pages of up to 100 records. Indeed controls visibility, relevance and pagination. Large or strict searches can deliver fewer rows than requested. Split broad research into separate role and city tasks.

**Why is a remote job missing?** Strict work-mode filtering requires an explicit source tag. Missing tags stay unknown rather than being treated as office work. Nationwide remote searches can use an appropriate location and keyword, but Indeed can still include adjacent results.

**What does experience filtering mean?** Only explicit numeric years-of-experience wording is used. Unknown experience is excluded by a strict filter. Fractional years and literal ranges are preserved; seniority labels are not converted into invented year counts. This currently works best with English descriptions.

**Why is some description text absent?** Contact and recruitment-contact lines are conservatively removed.  No personal contact directory is created. Description extraction does not guarantee perfect entity removal from every employer's prose.

**Can HTML pages be blocked while the Actor works?** Yes. The validated native data route is different from the ordinary HTML page. The internal endpoint or its public app identifier can change, and availability can differ by market. Refusals receive paced retries, followed by free notes if no useful response is available.

**Which source terms apply?** Indeed's terms govern use of its service and data. This is an independent tool with no Indeed affiliation or endorsement. Use the data for purposes you are permitted to undertake. Review the current source terms before deploying a commercial monitoring workflow.

# Actor input Schema

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

Job titles or skills, for example software engineer, nurse or sales. Each keyword is searched across the supplied cities. A global maxItems limit applies.

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

Cities, regions or postal codes in the selected country. Each keyword is searched across these locations. With no location, a major city is selected for US, GB, CA, AU and ZA; other countries search nationally.

## `experienceYears` (type: `number`):

Optional strict filter. Fetches descriptions to read explicitly stated years of experience. Jobs without an explicit experience statement are excluded. This can be substantially slower. No inferred seniority ranges.

## `workMode` (type: `string`):

SEEK work arrangement tag. An absent tag does not qualify for a strict work-mode filter.

## `postedWithinDays` (type: `number`):

Filter against the exact listing timestamp. For example 7 keeps jobs posted in the last 7 days. Zero keeps only jobs at the run timestamp or newer.

## `companies` (type: `array`):

Optional company names. Each name becomes an Indeed company: search, then the displayed employer is checked for a case-insensitive substring match. Use complete names for better coverage; short fragments can miss results.

## `startUrls` (type: `array`):

Optional HTTPS Indeed /jobs?q=...\&l=... searches or /viewjob?jk=... job links. Supports string URLs and {"url":"..."} entries at runtime. Supported markets: US, GB, CA, AU, NZ, ZA, IN, IE, DE, FR. URL q, l, radius and fromage are honored. Unsupported URL filters return a free note instead of being silently dropped.

## `maxItems` (type: `integer`):

Global limit, 1 to 5,000, after filtering and deduplication. Each search reads up to 10 pages of up to 100 jobs. Later inputs run only while space remains.

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

Include the full public job description with contact lines removed. Included in the same Job price; no extra detail fee. Company ratings and disclosed salary are always included when supplied.

## `onlyNewSinceLastRun` (type: `boolean`):

The first run delivers a baseline. Later runs deliver unseen job IDs whose dateOnIndeed is after the last completed watch window. A capped subsequent run keeps the old timestamp to drain its backlog. Same watchId reuses account-scoped history.

## `watchId` (type: `string`):

Stable name containing 1 to 80 letters, digits, underscores or hyphens. Reuse it for daily runs. Histories are scoped to this Actor and your account. An unfinished previous run must be resumed before the same watch continues.

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

Apify datacenter proxy first, then fresh country-matched residential sessions only if needed. Custom proxy settings are used on every attempt and never silently replaced.

## `salaryMin` (type: `number`):

Strict annual lower-bound filter in the selected market currency. Hourly and undisclosed salaries are excluded. No estimated salaries or currency conversions.

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

Indeed market. Choose a matching city. US and GB radius is miles; other markets use kilometres.

## `radius` (type: `number`):

0 to 100, in miles for US and GB, kilometres elsewhere. Indeed may include nearby or remote jobs.

## Actor input object example

```json
{
  "keywords": [
    "software engineer"
  ],
  "locations": [
    "New York, NY"
  ],
  "workMode": "any",
  "maxItems": 20,
  "includeDetails": false,
  "onlyNewSinceLastRun": false,
  "watchId": "default",
  "country": "US",
  "radius": 25
}
```

# Actor output Schema

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

No description

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

No description

## `summary` (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 = {
    "keywords": [
        "software engineer"
    ],
    "locations": [
        "New York, NY"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("moggedhq/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 = {
    "keywords": ["software engineer"],
    "locations": ["New York, NY"],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("moggedhq/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 '{
  "keywords": [
    "software engineer"
  ],
  "locations": [
    "New York, NY"
  ],
  "maxItems": 20
}' |
apify call moggedhq/indeed-jobs-scraper --silent --output-dataset

```

## MCP server setup

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