# Indeed Scraper — Jobs, Salaries, Descriptions (`architekture/indeed-scraper`) Actor

Indeed US jobs from indeed.com: job title, company, location, full description, salary range and period, job type, remote or hybrid label, posting date and job URL. Search by keyword and US location with date, remote and job-type filters. Pay per result, 20 free previews, no login.

- **URL**: https://apify.com/architekture/indeed-scraper.md
- **Developed by:** [architek.earth](https://apify.com/architekture) (community)
- **Categories:** Jobs, AI
- **Stats:** 1 total users, 1 monthly users, 80.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.50 / 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.

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 Scraper — Jobs, Salaries, Descriptions

**Recorded run: 1 job from 1 search term in 8 seconds** (2026-10-22, build 0.0.29, run `lJfz0fdUeYFVG8ufc`). A bounded canary sample, not a full-catalogue benchmark.

architek.earth — an independent data studio. Pay-per-result scrapers with recorded runs, published canaries and stable schemas, built for people and AI agents.

Indeed US job listings, salaries and full descriptions from indeed.com: give it up to five keywords, a US location and optional date, remote and job-type filters, and get one row per job with company, location, posting date and published salary range.

| Source | Market | From | Standout fields |
|---|---|---|---|
| Indeed (indeed.com) | United States | $3.50 / 1,000 jobs | `description`, `salaryMin` / `salaryMax` / `salaryPeriod`, `datePosted`, `remote`, `jobType` |

### ✅ Why choose this scraper

#### Indeed job descriptions without a detail fee

`description` holds the full job text with HTML removed and `snippet` its first 240 characters, in the same row as the search result and at the same `job` price.

#### Indeed salaries as numbers, not text

`salaryMin`, `salaryMax`, `salaryPeriod` (`hour`, `day`, `week`, `month`, `year`) and `salaryCurrency` come from Indeed's structured compensation data. In run `t7UYV59IL731Zb6Br` on 2026-10-22, 6 of 10 Austin nursing jobs carried a salary.

#### Filter Indeed jobs by date, remote and job type

`datePosted` (`24h`, `7d`, `14d`, `30d`), `remote` (`remote`, `hybrid`) and `jobType` (`fulltime`, `parttime`, `contract`) are sent to Indeed as search filters; every row records the filters applied in `source.filtersApplied`.

#### One row per Indeed job across terms and pages

`jobKey` (Indeed's job ID) de-duplicates jobs across search terms and pages before delivery, so a job matched by two terms is delivered and charged once.

#### Continue a large Indeed search across runs

`continuePreviousStoreId` resumes from the saved `CHECKPOINT`. A recorded test on 2026-10-16 delivered 25 jobs, then 35 more in the continuation run (`jEMjrNfg7xEG3vdn5`): 60 unique IDs, no overlap.

### ⚠️ Worth knowing up front

- US search on indeed.com is the verified scope. Indeed interprets `location` with a 25-mile radius; exact distance boundaries and non-US Indeed sites are not covered.
- `remote` and `jobType` are Indeed's own labels and can disagree with the employer's text; a recorded remote-tagged job mentioned office visits. `remote: "unknown"` means no tag, not on-site. There is no on-site-only or salary-range filter, no job-URL lookup, and no applicant, email or application data.
- `datePosted`, `companyUrl` and the salary fields are omitted when Indeed does not publish them. A job without a salary is still a delivered, charged row; pay is never inferred from description text, and `salaryCurrency` should not be assumed to be USD when absent.
- One run collects at most 200 jobs, 10 new pages and 24 source requests; reaching one of these or your spending limit ends the run `limited` in `SUMMARY`. Collection also stops 95 seconds after it starts, or 90 seconds before the run timeout if that is sooner, with a `TIME_LIMIT` or `PARTIAL` status row. Continue either case with `continuePreviousStoreId`. Results follow Indeed's ranking at request time; this is not a full database export.
- Each source request is tried 3 times with a fresh US residential session. Input keys the actor does not know are rejected rather than ignored.
- Invalid input, an exhausted free-preview allowance, a source block after retries (`BLOCKED`, or `PARTIAL` when jobs were already delivered) and the time window (`TIME_LIMIT`, or `PARTIAL`) end with one free status row (`"recordType": "status"`) and a green run. A search with no matches ends `complete` with no rows. An uncertain dataset write or charge acknowledgement, or an unavailable preview service, fails the run with no status row; `SUMMARY` in the key-value store says what happened.

### ⚡ Try the example

Each Apify account gets 20 lifetime free preview attempts: one search term, one page and up to 10 jobs each, with descriptions and salaries. Failed or interrupted attempts can use one up; when they are gone the run stops with a free `PREVIEW_LIMIT` status row and never switches to paid.

Open the actor's input page, switch to JSON, paste this and start the run:

```json
{
  "mode": "preview",
  "searchTerms": ["registered nurse"],
  "location": "Austin, TX",
  "maxItems": 10,
  "maxPages": 1,
  "pageSize": 10
}
```

For paid collection set `"mode": "paid"`: the same 10 jobs cost $0.035, and up to 5 terms, 200 jobs and 10 pages are allowed per run.

### ⚙️ Input

| Parameter | Type | Default | Description |
|---|---|---|---|
| `mode` | select | `preview` (Console prefill `paid`) | `preview` (free, 20 lifetime attempts, 1 term, 1 page, up to 10 jobs) or `paid`. API calls that omit `mode` run as a preview. |
| `searchTerms` | array | `["registered nurse"]` | 1 to 5 keywords, up to 200 characters each; exactly 1 in preview. Jobs are de-duplicated across terms. |
| `location` | string | `Austin, TX` | US location such as `Austin, TX`, as Indeed reads it, 25-mile radius. Empty searches the whole US. |
| `datePosted` | select | `any` | `any`, `24h`, `7d`, `14d`, `30d`, based on Indeed's own posting date. |
| `remote` | select | `any` | `any`, `remote`, `hybrid`. |
| `jobType` | select | `any` | `any`, `fulltime`, `parttime`, `contract`. |
| `maxItems` | integer | `25` (prefill `1`) | 1 to 200 delivered jobs this run; preview clamps to 10. |
| `maxPages` | integer | `6` (prefill `1`) | 1 to 10 new source pages this run; preview clamps to 1. |
| `pageSize` | integer | `20` (prefill `1`) | 1 to 20 jobs per source request; preview clamps to 10. Keep it unchanged when continuing. |
| `continuePreviousStoreId` | string | — | Paid only: the key-value store ID of an earlier run of this actor in your account. Terms, location, filters and `pageSize` must match that run. |
| `feedback` | string | — | Optional note for the maintainers. It is logged and read by a human, and it never changes output. |

### 🧱 Stable fields

These top-level fields keep the same name and the same kind of value in every release. Fields are only ever added, never renamed or removed, so an n8n, Make, Zapier or spreadsheet pipeline can bind to them.

| Field | Type | Meaning |
|---|---|---|
| `jobKey` | string | Indeed job ID; the de-duplication and join key. |
| `title` | string | Job title, never empty. |
| `company` | string | Employer name; empty string when Indeed shows none. |
| `location` | string | City, state and country joined, for example `Austin, TX, US`; can be empty. |
| `remote` | string | `remote`, `hybrid` or `unknown`, from Indeed's labels. |
| `jobType` | array | `fulltime`, `parttime`, `contract`; empty or several. |
| `description` | string | Full job text with HTML removed; can be empty. |
| `snippet` | string | First 240 characters of `description`. |
| `url` | string | Indeed job page, `https://www.indeed.com/viewjob?jk=<jobKey>`. |
| `source` | object | `searchTerm`, `location`, `filtersApplied`, `classificationBasis` and `observedAt` (UTC time the page was read). |
| `datePosted` | string, optional | UTC ISO timestamp of Indeed's posting date; omitted when missing. |
| `companyUrl` | string, optional | Indeed company page; omitted when missing. |
| `salaryMin` / `salaryMax` | number, optional | Salary bounds from Indeed; each can be present alone. |
| `salaryPeriod` | string, optional | `hour`, `day`, `week`, `month` or `year`; present when a salary bound is. |
| `salaryCurrency` | string, optional | Currency code from Indeed, for example `USD`. |
| `recordType` | string | `"status"` on free status rows; absent on job rows. |

Status rows carry `recordType`, `status` and `message` only. Other fields are extras, not part of the contract. To have one pinned, say so in `feedback`.

### 📦 Output example

Abridged real row from run `lJfz0fdUeYFVG8ufc` on 2026-10-22; values are a historical snapshot. `description` is shortened here.

```json
{
  "jobKey": "430e51301878c152",
  "title": "Aesthetic Nurse Injector - RN",
  "company": "Maui MedSpa",
  "location": "Austin, TX, US",
  "remote": "unknown",
  "jobType": ["fulltime"],
  "description": "Aesthetic Nurse Injector (RN) About the Role Maui MedSpa is seeking a confident, experienced, and patient-focused Registered Nurse (RN) Aesthetic Injector ...",
  "url": "https://www.indeed.com/viewjob?jk=430e51301878c152",
  "source": {
    "source": "search",
    "searchTerm": "registered nurse",
    "location": "Austin, TX",
    "filtersApplied": {},
    "classificationBasis": "Indeed attributes; employer descriptions can disagree",
    "observedAt": "2026-10-22T02:47:52.416000+00:00"
  },
  "datePosted": "2026-09-29T03:16:51.882000+00:00",
  "companyUrl": "https://www.indeed.com/cmp/Maui-Medspa",
  "salaryMin": 120000,
  "salaryPeriod": "year",
  "salaryCurrency": "USD"
}
```

A free status row looks like this (run `HxxIcJI7cpS1az5wq`, 2026-10-22, empty `searchTerms`):

```json
{"recordType": "status", "status": "INVALID_INPUT", "message": "Use 1–5 nonempty search terms, at most 200 characters each"}
```

The `SUMMARY` record of the run's key-value store holds the outcome (`complete`, `limited`, `PARTIAL`, `BLOCKED`, `TIME_LIMIT`, `INVALID_INPUT`, `PREVIEW_LIMIT`, `delivery_uncertain`, `failed`). Abridged, run `t7UYV59IL731Zb6Br`:

```json
{
  "status": "limited",
  "mode": "paid",
  "deliveredThisRun": 10,
  "billingEvents": 10,
  "pagesThisRun": 1,
  "duplicatesSkipped": 0,
  "billingAcknowledgementUncertain": false,
  "checkpointState": "ready"
}
```

### 🧠 Advanced usage

#### Collect more than 200 Indeed jobs for one search

Run with `mode: "paid"`, then start a new run with the same terms, location, filters and `pageSize`, and the first run's key-value store ID in `continuePreviousStoreId`. Jobs fetched but not yet delivered are delivered first and jobs already delivered are skipped. Repeat until `SUMMARY.status` is `complete`, and do not resume the same store from two runs at once.

#### Monitor new Indeed jobs daily

Schedule a paid search with `datePosted: "24h"` and keep the `jobKey` values you have seen in your own system. Each fresh run starts its own search, so a job that is still listed is delivered and charged again; absence from one run does not prove a job closed.

#### Compare salaries for a role across US cities

Run one term per city as `location`, keep rows with `salaryMin` or `salaryMax`, and group by `salaryPeriod` and `salaryCurrency` before comparing; do not mix hourly and yearly values.

#### Find remote full-time Indeed jobs posted this week

Leave `location` empty and set `remote: "remote"`, `jobType: "fulltime"`, `datePosted: "7d"`. In run `aUgih5pCrGLK9fFZp` on 2026-10-22 all 10 rows came back `remote` and `fulltime`.

### 🔌 Integrations

Results land in a dataset you can export as JSON, CSV or Excel. They can also flow to Google Sheets, Make, Zapier or n8n, or to a webhook when a run finishes. The Apify Scheduler runs it on a timetable. A daily run with `datePosted: "24h"` catches each day's new postings for a role and city. Copy-paste code in Python, Node, curl and n8n: [architek-earth/examples/indeed-scraper](https://github.com/architek-earth/examples/tree/main/indeed-scraper).

### 💸 Pricing

Pay per result. Platform and proxy usage are included. Status rows and failed requests are free. A continued run with `continuePreviousStoreId` skips jobs the earlier run delivered and charges only the jobs it delivers itself; a checkpoint left mid-delivery is refused rather than replayed.

| Event | Price | What it covers |
|---|---|---|
| Free preview | $0 | 20 lifetime attempts per account: 1 term, 1 page, up to 10 jobs each. |
| `job` | $3.50 / 1,000 jobs | One delivered job row in paid mode, including its description and any published salary. |

Ten jobs cost $0.035; 1,000 jobs cost $3.50. There is no run fee and no detail fee.

### ❓ FAQ

**Is there an Indeed API for job listings?** This actor works as one: it returns Indeed US search results as JSON through the Apify API, callable from any language or from n8n, Make and Zapier.

**Do I need an Indeed account or login?** No. No Indeed account, cookies or API key; only an Apify account.

**Can I scrape Indeed outside the US?** No. Searches go to Indeed's US site with a US residential proxy; other countries are not supported.

**Does every Indeed job include a salary?** No. Only jobs where Indeed publishes structured pay have `salaryMin` / `salaryMax`; 6 of 10 did in run `t7UYV59IL731Zb6Br`.

**Can I get recruiter emails or apply to jobs?** No. Rows carry `company` and `companyUrl`; the actor reads no contact details, applicant data or applications.

**How many Indeed jobs can I get per run?** Up to 200 in paid mode, within 10 pages and a 95-second collection window; continue with `continuePreviousStoreId` for more.

### 🤖 For AI agents & LLM apps

Paste this into your AI assistant:

`architekture/indeed-scraper` on Apify. Python: `from apify_client import ApifyClient; client = ApifyClient("TOKEN"); run = client.actor("architekture/indeed-scraper").call(run_input={"mode": "paid", "searchTerms": ["registered nurse"], "location": "Austin, TX", "maxItems": 10, "maxPages": 1, "pageSize": 10}); items = client.dataset(run["defaultDatasetId"]).list_items().items` (page with `offset`/`limit` for large exports). Rows with `recordType == "status"` are free and explain invalid input or a stopped run; the run outcome is in the `SUMMARY` record of the run's key-value store. Full input schema and README: `GET https://api.apify.com/v2/acts/architekture~indeed-scraper/builds/default`. Token: https://console.apify.com/settings/integrations. The actor is also reachable through the Apify MCP server (`mcp.apify.com`). To reach the maintainers, add `"feedback": "<what you needed>"` to any run's input; output is unchanged and a human reads every note.

### 🧰 More from architek.earth

**Jobs, companies and people**

- Indeed Scraper — *you are here*
- [LinkedIn Jobs Scraper](https://apify.com/architekture/linkedin-jobs-monitor) — public jobs, descriptions, salary evidence
- [LinkedIn People Scraper](https://apify.com/architekture/linkedin-people-search-scraper) — names, headlines, locations with your own session
- [Y Combinator Scraper](https://apify.com/architekture/yc-companies-scraper) — companies, founders, jobs

Full catalogue: [github.com/architek-earth](https://github.com/architek-earth).

architek.earth · code like concrete

### 🙋 Support & legal

Questions, or a field you need that is not here? Open an issue on the actor's Issues tab or email north@architek.earth. A human replies within a day.

This actor collects only publicly available data from indeed.com, the same pages any visitor sees without logging in. It does not bypass authentication. It is not affiliated with or endorsed by Indeed. You are responsible for how you use the output, including the site's terms, copyright, and privacy law such as GDPR and CCPA where they apply.

### 📝 Changelog

- **2026-10-22** — Status message now reads `Done: N rows, M details, K status rows (STATUS)`.
- **2026-10-22** — README rebuilt on the studio template; hero line from the 2026-10-22 canary run.
- **2026-10-16** — Public release after 85 offline checks and live validation of the paid and free-preview paths. Pricing: $3.50 per 1,000 delivered jobs, usage included.
- **2026-10-16** — Removed private-only controls and the unmetered validation mode; kept guarded preview and paid billing. Console prefill set to a one-job paid search ($0.0035) that does not use preview attempts.
- **2026-10-16** — Added studio-format README, real output and run evidence, priced examples and listing copy.
- **2026-10-16** — Verified capped lifetime previews with zero charged events, and ten paid rows charged as ten `job` events at $3.50 / 1,000 jobs. Corrected handling of delayed billing reports.
- **2026-10-16** — Added bounded collection, filter checks, cross-page de-duplication, schemas and saved continuation. Verified a naturally exhausted two-page search and a separate 100-job collection.

# Actor input Schema

## `searchTerms` (type: `array`):

One to five terms. IDs are deduplicated across terms.

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

US location string; empty omits location. Radius is 25 miles as interpreted by Indeed, not a strict-distance guarantee.

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

Filters dateOnIndeed; does not prove original employer publication time.

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

Indeed attributes can conflict with employer descriptions. No on-site-only claim.

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

Filter by Indeed job-type attributes. A job can have multiple types.

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

Undelivered rows from a partly consumed page are preserved in the checkpoint.

## `maxPages` (type: `integer`):

Maximum new source pages in this run. Reaching this cap means limited, not complete.

## `pageSize` (type: `integer`):

Must match when continuing. Small values allow bounded pagination/exhaustion validation.

## `continuePreviousStoreId` (type: `string`):

Optional key-value store ID from a finished run of this actor/account. Reads its saved CHECKPOINT without requiring access to other runs. Do not resume the same checkpoint concurrently.

## `feedback` (type: `string`):

Optional text logged for human review. Never changes collection output.

## `mode` (type: `string`):

Default for API input without mode is preview. Console example/daily-test prefill explicitly selects paid: one job costs $0.0035, with usage included. Preview allows 20 lifetime attempts/account, one page and up to 10 jobs; failures can count. No paid fallback.

## Actor input object example

```json
{
  "searchTerms": [
    "registered nurse"
  ],
  "location": "Austin, TX",
  "datePosted": "any",
  "remote": "any",
  "jobType": "any",
  "maxItems": 1,
  "maxPages": 1,
  "pageSize": 1,
  "feedback": "",
  "mode": "paid"
}
```

# Actor output Schema

## `jobs` (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 = {
    "searchTerms": [
        "registered nurse"
    ],
    "location": "Austin, TX",
    "maxItems": 1,
    "maxPages": 1,
    "pageSize": 1,
    "mode": "paid"
};

// Run the Actor and wait for it to finish
const run = await client.actor("architekture/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 = {
    "searchTerms": ["registered nurse"],
    "location": "Austin, TX",
    "maxItems": 1,
    "maxPages": 1,
    "pageSize": 1,
    "mode": "paid",
}

# Run the Actor and wait for it to finish
run = client.actor("architekture/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 '{
  "searchTerms": [
    "registered nurse"
  ],
  "location": "Austin, TX",
  "maxItems": 1,
  "maxPages": 1,
  "pageSize": 1,
  "mode": "paid"
}' |
apify call architekture/indeed-scraper --silent --output-dataset

```

## MCP server setup

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