# Indeed Jobs Scraper (`nice_dev/indeed-jobs-scraper`) Actor

Scrape Indeed jobs in 60+ countries by keyword, location or URL, beyond the 1,000-job limit: title, full description, salary, location, job type, remote, company profile and rating. Export to JSON, CSV or Excel.

- **URL**: https://apify.com/nice_dev/indeed-jobs-scraper.md
- **Developed by:** [Nice Dev](https://apify.com/nice_dev) (community)
- **Categories:** Jobs, Lead generation, MCP servers
- **Stats:** 3 total users, 3 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

### 💼 What is Indeed Jobs Scraper?

**Indeed Jobs Scraper** extracts **job postings from [Indeed](https://www.indeed.com)** in **62 countries**: **title, full description, salary, location with GPS, job type, remote type, experience level and the employer's profile** (website, size, revenue, sector, rating, reviews), for any keyword, place or search URL. It works as an **Indeed API alternative**: run it on a schedule, call it from your code, or plug it into Make, Zapier or n8n.

Type a **keyword** (`software engineer`) and a **location**, or paste any Indeed search URL, click **Start**, and download the jobs in JSON, CSV or Excel. Pick several countries, keywords and locations in one run, filter on the contract type, remote work, level, salary and date, or tick **Only new jobs** to monitor a market. No login, nothing to set up.

### 📋 What data can you extract from Indeed?

One item per job, 115 fields:

| Category | What you get |
| --- | --- |
| 🏷️ **Job** | title, Indeed link, job key — `Senior Software Engineer` |
| 🏢 **Company** | name, Indeed company page, website, logo, sector, size, revenue, founding year, CEO, headquarters, parent group, the sections of its company page — `HubSpot` |
| ⭐ **Reviews** | Indeed rating and review count, ratings by theme (pay, culture, management…), CEO approval, work wellbeing, interview difficulty and length — `3.9 from 20,438 reviews` |
| 📍 **Location** | city, state, county, postal code, street address, country, latitude and longitude — `Jersey City, NJ 07310` |
| 💰 **Salary** | minimum, maximum, period (hour, month, year…), currency and the text Indeed shows, in the language of the country's site — `$137,750 - $195,000 a year` |
| 🧭 **Work type** | full-time, part-time, contract…, remote / hybrid / on-site, experience level — `hybrid` |
| 📝 **Description** | full text, HTML or Markdown of the job, requirements, Indeed tags (skills, benefits, education, licences) |
| 🕒 **Dates** | when the employer published the job, when it appeared on Indeed, expiry — what the date filters read |
| 🔗 **Apply** | employer or ATS page, Indeed Apply or not, openings, urgent and high-volume hiring, employer response rate |
| 📞 **Contacts** (option) | e-mails and phone numbers the employer wrote in the job text |
| 🏭 **Company rows** (option) | one row per employer: profile, open jobs now, new jobs in 24 hours / 7 days / 30 days, change since the previous run |

Every field, with an example, is listed in the **Output** section below.

### ✅ Why use Indeed Jobs Scraper?

- 🌍 **62 Indeed countries in one run**: several countries × several keywords × several locations, each search with its own cap.
- 🎯 **Filters applied by Indeed**: job type, remote work, experience level, salary, date, language, radius — fewer pages to read, nothing to pay for the jobs left out.
- 🧩 **Rich rows**: the full description, salary, GPS location and the employer's profile and ratings in every job.
- 🔁 **Nothing twice**: a job found by several searches is saved — and charged — once.
- 🔔 **Monitoring built in**: tick **Only new jobs**, schedule the Actor, and each run returns (and charges) only what it has never delivered before.
- 🔌 API, scheduling, monitoring, integrations (Make, Zapier, n8n, Google Sheets…), proxy rotation and JSON/CSV/Excel export via the Apify platform.

### 🚀 How to scrape Indeed

1. Create a free Apify account.
2. Open **Indeed Jobs Scraper**, type a **Search keyword** (e.g. `software engineer`) and a **Location** (e.g. `New York, NY`), and pick the **Country**.
3. Or paste your own Indeed URLs into **Start URLs**: any search results page (keyword, place, radius, date, sort and job-type filters are kept) or single job pages; or give **Job keys**.
4. Set **Max jobs** (100 by default, 0 = no limit) — and **Max jobs per search** when you run several searches — then click **Start**.
5. Download the dataset in JSON, CSV, Excel or via API.

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

This Actor uses **pay per event** pricing: a price per 1,000 jobs saved plus a small fee per run start — the amounts are on the **Pricing** tab of this page. Filters Indeed does not offer are applied by the Actor on every job it reads: a job you keep costs its normal price, a job a filter drops costs a smaller filter fee (`filter-check`) — filtering never costs more than taking everything. Filters applied by Indeed itself (the **Job filters** section of the form) cost nothing for the jobs left out. Platform usage (compute, proxy) is included in the price.

### ⚙️ Input

```json
{
    "query": "software engineer",
    "location": "New York, NY",
    "country": "US",
    "maxItems": 200
}
```

Several countries and keywords, only recent full-time jobs paying at least 60,000 a year, only the ones not delivered before:

```json
{
    "searchQueries": ["nurse", "teacher"],
    "countries": ["GB", "IE"],
    "jobTypes": ["fulltime"],
    "minSalary": 60000,
    "datePosted": "7d",
    "maxItemsPerQuery": 50,
    "onlyNew": true,
    "stateKey": "nurses-uk"
}
```

Or with your own URLs and job keys:

```json
{
    "startUrls": [
        { "url": "https://www.indeed.com/jobs?q=nurse&l=Texas&fromage=7&sort=date" },
        { "url": "https://www.indeed.com/viewjob?jk=4f9a1c2b3d4e5f60" }
    ],
    "maxItems": 500
}
```

| Field | Notes |
| --- | --- |
| `query`, `searchQueries` | Keyword as typed on Indeed (`software engineer`, `title:(nurse)`); `searchQueries` adds more keywords (one search each). Empty = every job of the location. Ignored when `startUrls` is set. |
| `location`, `locations` | City, state, region or postal code (`New York, NY`, `Paris`); `locations` adds more places. Every keyword is searched in every location (max 500 searches per run). Empty = the whole country. |
| `country`, `countries` | The Indeed country site to search (`US`, `GB`, `FR`, `DE`… 62 countries); `countries` lists more: with `country` left on `US`, only the countries of the list are searched (put `US` in the list to include it), another `country` is added to them. Every keyword × location is searched in each (max 500 searches per run). With no keyword, location, URL, job key or company, the default country alone runs a 20-job example; any other country, or `countries` filled (for the whole US, put `US` in `countries`), reads every job of the country up to `maxItems` (`onlyNew` allowed). |
| `startUrls` | Indeed URLs of any country: search results pages (keyword, place, radius, age, sort and job-type filters kept; pagination automatic), company job pages (`https://www.indeed.com/cmp/Hubspot/jobs`: all the open jobs of that employer, or only those of its own keyword and place boxes: `https://www.indeed.com/cmp/Hubspot/jobs?q=engineer&l=Boston`) or single job pages (`https://www.indeed.com/viewjob?jk=4f9a1c2b3d4e5f60`). |
| `jobKeys` | Job keys to read directly (`4f9a1c2b3d4e5f60`), on top of the keyword and location searches (empty them to read only these jobs): expired jobs are returned too, flagged as expired. Up to 10,000. The `datePosted`, `jobTypes`, `remoteType`, `experienceLevels`, `minSalary` / `maxSalary` and `jobLanguage` filters are checked on each job, also for a single job link of `startUrls`: a job that does not show the criterion is dropped and counted as filtered out (the pay is compared per year, so a pay by the day or the hour does not match); the radius and the location of a search cannot be read on a job and are not applied. A job link is read on the site of the link, a bare key on the **Country** site — on the first of `countries` when Country is left on United States and that list is filled: give the country of the job to get its pay. |
| `companies` | All the open jobs of these employers: company names (`Starbucks`), website domains (`starbucks.com`) or employer keys (`afd1466db269b253`). Each entry is matched to the exact Indeed employer, never to a look-alike: a name with no exact match is skipped, and the log names the employers Indeed lists under that name (give the exact name, or paste the Indeed company page in Start URLs). Combined with the keywords and locations when they are filled; ignored when Start URLs are set. |
| `maxItems`, `maxItemsPerQuery` | Stop after this many jobs for the whole run, and for each search (`0` = unlimited). |
| `splitLargeSearches` | Indeed shows at most 1,000 jobs per search. When a search has more, the Actor counts it by publication date (a counter call, free), cuts it into date slices and reads each one: nearly all of a large search (99.9 % measured on searches of 2,500 to 13,700 jobs) instead of the first 1,000. The log and the **COVERAGE** record of the key-value store give announced against collected jobs per search; a slice of one hour still above 1,000 jobs is read up to 1,000 and reported. Off = the first 1,000 only. |
| `descriptionFormat` | Which forms of the description to save: `text_html`, `all` (text, HTML and Markdown), `text`, `html`, `markdown`, `none`. |
| `extractContacts`, `companySummary` | **Extra-charge options, both off by default** (the events are on the Pricing tab). Contacts: the e-mail addresses and phone numbers the employer wrote in the job text are added to each job that has one, and only those jobs are charged (on 700 jobs read in the US, France and Germany, 19 % had an e-mail and 8 % a phone number). Company summary: one extra row per employer of the run, not counted in Max jobs and charged per row. |
| `datePosted` | Only jobs added to Indeed in this period: `24h`, `3d`, `7d`, `14d`, `30d`, `any`. |
| `jobTypes` | Keep jobs of ANY of these types: `fulltime`, `parttime`, `contract`, `temporary`, `internship`, `permanent`, `fixedterm`. Outside the USA the fixed-term contract Indeed shows as « Contract » (« CDD », « Befristeter Vertrag »…) is `fixedterm`: `contract` is the contractor, mostly in the USA, Canada and Australia. |
| `remoteType` | Work arrangement as tagged by Indeed: `any`, `remote`, `hybrid`, `onsite`. |
| `experienceLevels` | Keep jobs of ANY of these levels: `entry`, `mid`, `senior`. |
| `industries` | Sectors of the employer: a name (`Healthcare`) or an Indeed sector key (`22VKN`): names or keys in one run, not both. |
| `minSalary`, `maxSalary` | Yearly pay bounds in the country's currency, Indeed's estimates included (`0` = none). |
| `radius` | Distance around the location (`25`; `0` = the exact place). |
| `radiusUnit` | Unit of the radius: `miles` or `km`. |
| `jobLanguage` | Only jobs written in this language: two letters (`en`, `fr`, `de`). |
| `sortBy` | `relevance` or `date` (newest first). |
| `postedAfter`, `postedBefore` | Date the job was added to Indeed: `2026-09-01` (dates are read in UTC), or a period before now (`7 days`, `2 weeks`, `1 month`, `24 hours`). |
| `excludeKeywords`, `excludeCompanies` | Drop the jobs whose title (or the cleaned-up title Indeed shows, `displayTitle`) contains one of these words, or whose company name as shown (`companyName`, its Indeed company page name `companyProfileName`, or its parent group `parentCompanyName`) does (case and accents ignored). |
| `onlyWithSalary` | Drop the jobs that show no pay. |
| `applyMethod` | `any`, `easyApply` (Indeed Apply only) or `external` (the employer's or ATS page). |
| `minCompanyRating` | Drop the jobs of employers rated below this (out of 5, `4`); unrated employers are dropped too. |
| `onlyNew`, `stateKey`, `resetState` | Monitoring: only the jobs never delivered under this memory key (`nurses-uk`); `resetState` forgets the memory. |
| Advanced | `proxyConfiguration` (Apify proxy by default, included in the price; the residential proxy is not available), `maxConcurrency` (lowered by itself to what the memory of the run holds: 5 at the default 512 MB, 17 at 1 GB), `maxRequestsPerMinute`, `minRequestIntervalMs`, `maxRequestRetries`, `debugLog`. |

### 📦 Output

A real item, shortened:

```json
{
    "recordType": "job",
    "id": "b08f834bb5fd1e8d",
    "url": "https://www.indeed.com/viewjob?jk=b08f834bb5fd1e8d",
    "title": "Senior Associate - Machine Learning Engineer",
    "normalizedTitle": "machine learning engineer",
    "occupations": [{ "key": "EHPW9", "label": "Technology Occupations" }],
    "companyName": "JPMorganChase",
    "companyProfileName": "JPMorganChase",
    "companyUrl": "https://www.indeed.com/cmp/Jpmorganchase-2",
    "companyWebsite": "https://www.jpmorganchase.com/",
    "companyIndustry": "Banking & Lending",
    "companySize": "10,000+",
    "companyRating": 3.9,
    "companyReviewCount": 20438,
    "companyRatingHistogram": [1189, 1269, 4097, 6736, 7147],
    "location": "Jersey City, NJ 07310",
    "city": "Jersey City",
    "regionCode": "NJ",
    "country": "US",
    "latitude": 40.728752,
    "longitude": -74.03531,
    "remoteType": null,
    "isRemote": false,
    "jobTypes": ["fulltime"],
    "experienceLevel": "mid",
    "salaryMin": 137750,
    "salaryMax": 195000,
    "salaryPeriod": "year",
    "salaryCurrency": "USD",
    "salaryText": "$137,750 - $195,000 a year",
    "descriptionText": "JOB DESCRIPTION As a Senior Associate, Data Scientist / Machine Learning Engineer within our…",
    "language": "en",
    "publishedAt": "2026-10-05T05:00:00.000Z",
    "dateOnIndeed": "2026-10-05T23:25:50.472Z",
    "isExpired": false,
    "applyUrl": "https://JPMorganChase.contacthr.com/153829834",
    "isEasyApply": false,
    "searchQuery": "software engineer",
    "searchLocation": "New York, NY",
    "searchUrl": "https://www.indeed.com/jobs?q=software+engineer&l=New+York%2C+NY",
    "rank": 1,
    "scrapedAt": "2026-10-06T00:52:01.531Z"
}
```

You can download the dataset in various formats such as JSON, HTML, CSV or Excel. A field Indeed does not give for a job is `null` (or an empty list), never missing.

#### All 115 fields

| Fields | What you get |
| --- | --- |
| `recordType`, `id`, `url` | `job`, the job key, the Indeed link of the job on its country site |
| `title`, `normalizedTitle`, `displayTitle`, `normalizedCategory`, `occupations` | the title, Indeed's standard title, its cleaned-up title, Indeed's own category of the job (`techsoftware`, `mednurse`, `retail`; searches only), Indeed's occupation categories |
| `companyName`, `companyProfileName`, `companyKey`, `parentCompanyName`, `companyParentUrl`, `companyUrl`, `companyWebsite`, `companyLogo`, `companyHeaderImage` | the employer as the job shows it, the name of its Indeed company page (the group's, for an establishment), its Indeed id, its parent group and the group's Indeed page, its Indeed page, website and images |
| `companyIndustry`, `companySize`, `companyRevenue`, `companyFounded`, `companyCeo`, `companyStockSymbol`, `companyHeadquarters`, `companyDescription`, `companyBriefDescription` | the employer's profile: `Banking & Lending`, `10,000+`, `more than $10B (USD)`, `1799` |
| `companyTier`, `companyClaimed`, `companyCeoPhoto`, `companyBrandColor`, `companySocialLinks` | the Indeed account type, whether the company page is managed, CEO photo, brand colour, social links |
| `companyRating`, `companyReviewCount`, `companyReviewCountInCountry`, `companyRatingWorkLifeBalance`, `companyRatingCompensationBenefits`, `companyRatingCulture`, `companyRatingJobSecurityAdvancement`, `companyRatingManagement`, `companyRatingHistogram` | Indeed's ratings out of 5, overall and by theme, the review count (all countries, and those written in the country of the site), the number of 1 to 5 star reviews |
| `companyCeoApproval`, `companyCeoApprovalCount`, `companyRecommendYes`, `companyRecommendNo` | CEO approval (0 to 1) and its votes, who would and would not recommend the employer |
| `companyWellbeingScore`, `companyWellbeingGrade`, `companyWellbeingResponses`, `companyHappinessScore`, `companyHappinessGrade` | Indeed's work wellbeing and happiness scores (0 to 100), grades and number of answers |
| `companyCustomContents`, `companyInterviewDifficulty`, `companyInterviewExperience`, `companyInterviewProcessLength`, `companyInterviewResponses` | the sections the employer wrote on its company page (why join us, benefits: title, text, picture or video); how candidates rate the interview (`EASY`, `MEDIUM`, `HARD`; `POOR`, `AVERAGE`, `EXCELLENT`), how long hiring takes (`A_WEEK`…) and how many answered |
| `location`, `city`, `region`, `regionCode`, `county`, `countyCode`, `neighborhood`, `postalCode`, `streetAddress` | `Jersey City, NJ 07310`, `Jersey City`, `New Jersey`, `NJ`, `Hudson County`, `017`… |
| `country`, `countryName`, `latitude`, `longitude` | the Indeed country site (`US`), its name, the coordinates (`null` for a job placed at « Remote »: Indeed gives it no real place) |
| `remoteType`, `isRemote`, `jobTypes`, `experienceLevel` | `remote`, `hybrid` or `onsite` (a job placed at « Remote » is `remote`); `fulltime`, `parttime`, `contract`…; `entry`, `mid` or `senior` |
| `salaryMin`, `salaryMax`, `salaryPeriod`, `salaryCurrency`, `salaryText`, `salarySource` | `137750`, `195000`, `year`, `USD`, the pay as Indeed shows it, `ESTIMATED` when the pay is Indeed's estimate |
| `attributes`, `employerAttributes`, `requirements`, `workSchedule`, `screenerQuestions` | every Indeed tag of the job (type, skills, benefits, education, licences), those the employer chose, the requirements the employer set, the schedule it wrote (`Night`), what it asks candidates to confirm before applying |
| `descriptionText`, `descriptionHtml`, `descriptionMarkdown`, `language` | the full description as text, HTML and Markdown (the forms the Description format asks for), its language |
| `publishedAt`, `dateOnIndeed`, `dateLastAnnotated`, `expiresAt`, `isExpired` | `2026-10-05T05:00:00.000Z` when the employer published it, when it appeared on Indeed (what the date filters read), when Indeed last processed it (searches only), expiry, expired or not |
| `applyUrl`, `isEasyApply`, `hiringCount`, `isHighVolumeHiring`, `isUrgentHire` | the employer or ATS page, Indeed Apply or not, number of openings, hiring flags |
| `employerResponseRate`, `employerResponseDays`, `employerActivelyReviewing`, `sourceType`, `sourceName`, `refNum` | how often and how fast the employer answers, whether Indeed says it actively reviews applications, who posted the job (`EMPLOYER`, `RECRUITER`), the source, the employer's own reference |
| `emails`, `phones` | e-mails and phones written in the job text (with the Contacts option; empty lists otherwise) |
| `openJobs`, `newJobs24h`, `newJobs7d`, `newJobs30d`, `openJobsChange`, `jobsInRun` | the summary row of a company (with the Company summary option: the row has `recordType` `company`, the employer's fields above and these counters; `null` on a job) |
| `searchQuery`, `searchLocation`, `searchUrl`, `rank`, `scrapedAt` | the keyword, the place and the Indeed search that found the job, its position in it (1 = first), the extraction time |

### 💡 Tips

#### How to get more results

Indeed shows at most 1,000 jobs per search (10 pages of 100). With **Get past the 1,000-job limit** on (the default) the Actor goes further by itself: it counts the search by publication date, cuts it into slices that each fit in Indeed's window, reads them newest first and saves a job found twice once — set `maxItems` to `0` for everything. The **COVERAGE** record of the run's key-value store says, per search, how many jobs Indeed announces and how many were collected. A search read with a cap of 1,000 or less is not cut. You can still add keywords, locations, a narrower radius or the job-type filters, one search each.

To follow one employer, give its name in `companies` (or paste its Indeed company page in Start URLs): the Actor finds the exact Indeed employer and reads all its open jobs, with the same slicing.

#### How to reduce costs

The price is per job, so the levers are `maxItems`, `maxItemsPerQuery`, the filters of the **Job filters** section (Indeed applies them: the jobs left out are not read and cost nothing) and `onlyNew` for recurring runs (you never pay twice for the same job). Choose `descriptionFormat` `none` for a list without the descriptions: the runs are lighter, the price per job is the same.

#### Several searches in one run

Fill `searchQueries`, `locations` and / or `countries`: the Actor runs one search per country × keyword × location (3 keywords × 4 cities × 2 countries = 24 searches, up to 500 per run). The single `query`, `location` and `country` fields still work and are added to the lists. A job found by several searches is saved — and charged — once. Set `maxItemsPerQuery` to give every search its own cap: without it the first searches can use up the whole `maxItems` budget. You can also paste several search URLs into `startUrls`: each one is a search of its own, with the same cap.

#### Monitoring: only the new jobs

Tick **Only new jobs** (`onlyNew`) and schedule the Actor. The first run returns everything; each later run skips the jobs already delivered: they are not saved and not charged. The memory lives in a named key-value store of your account (`indeed-jobs-scraper-seen`, up to 150,000 jobs per key) and is only updated with jobs that really reached the dataset, so a failed run never hides anything. Give each schedule its own `stateKey` (two schedules sharing a key would hide each other's jobs), and tick `resetState` once to start over. Searches are then read newest first, and a search stops once it meets 500 jobs in a row you already have. A job that does not pass a time-dependent filter yet (a relative "posted before" date) is checked again on the next run.

#### Filter by publication date

`datePosted` keeps the jobs added to Indeed in the last 24 hours, 3, 7, 14 or 30 days. `postedAfter` and `postedBefore` take a date (`2026-09-01`, the whole day is included, in UTC) or a period before now (`7 days`, `2 weeks`, `1 month`; via the API also `24 hours` or a full ISO date-time). They read the date the job appeared on Indeed (`dateOnIndeed`); Indeed itself cuts the pages, and the Actor keeps the exact bounds. A job without a date is dropped as soon as a date bound is set. Filtered-out jobs are not saved and do not count in `maxItems` (each job the Actor itself drops costs the filter fee, see Pricing); the run summary tells how many were filtered. With `postedAfter`, searches are read newest first and stop at the first page that is entirely too old.

### 🔌 Integrations and API

Call the Actor via the Apify API, the JavaScript or Python clients, or connect it with integrations and webhooks (Make, Zapier, n8n, Google Sheets, Slack, Airtable…). The dataset can be fetched as JSON or CSV from any tool.

### 🤖 Use with AI agents (MCP)

AI agents (Claude, ChatGPT, Cursor…) can find and run this Actor through the [Apify MCP server](https://mcp.apify.com), billed to their Apify account like any run. It returns one item per Indeed job. Actor id: `nice_dev/indeed-jobs-scraper`; MCP server with this Actor only: `https://mcp.apify.com/?tools=fetch-actor-details,nice_dev/indeed-jobs-scraper`.

Smallest input, for a cheap first call:

```json
{
    "query": "software engineer",
    "location": "New York, NY",
    "maxItems": 10
}
```

Key output fields: `title`, `companyName`, `location`, `salaryText`, `jobTypes`, `remoteType`, `dateOnIndeed` and `url`.

Cost: the price per 1,000 jobs plus the run-start fee, see the pricing section above. Cap each call with `maxItems` and, through the API, with the run option `maxTotalChargeUsd`.

### ❓ FAQ

#### Is it legal to scrape Indeed?

The Actor only reads the job postings Indeed shows publicly to any anonymous visitor. It logs in to nothing. Results can contain personal data (a recruiter's name or e-mail written in a job text), which is protected by GDPR: do not store it without a legitimate reason. You are responsible for using the data in compliance with Indeed's Terms of Use and applicable law. This Actor is not affiliated with Indeed.

#### Does it need a login or a proxy?

No login. The proxy is included in the price: leave the default setting (the residential proxy is not available). A request Indeed turns away is retried at once on a new proxy session, up to 10 times on top of the retries (without a proxy, after a pause of 5 seconds, doubled at each retry up to 60 seconds).

#### Is the data safe to open in Excel or to show on a web page?

Titles and descriptions are the employers' own words, copied as they are. A text can begin with `-`, `+`, `=` or `@` (a phone number, a title such as `-20% today`): Excel and Google Sheets may read such a cell of a CSV file as a formula or as a number. The Actor leaves the text as it is, so that the JSON and the API give the real value: when you open a CSV, import these columns as text. Every URL field holds an http(s) URL or `null`. `descriptionHtml` is the employer's own HTML, which the Actor does not sanitize: on a web page, escape every field like any text written by a stranger.

#### Known limitations

- Indeed shows at most 1,000 jobs per search; the date slices recover nearly all of a large search (99.9 % measured on 3 searches of 2,500 to 13,700 jobs), not all of it (jobs added while the run goes on can fall between two slices). A slice of one hour above 1,000 jobs is read up to 1,000 and reported in COVERAGE. With `sortBy` relevance, a split search is read newest first.
- A company name needs an exact match with an Indeed employer: « Amazon » has no employer of that exact name (give `Amazon.com`, or paste the company page). A run whose companies match none of them fails with the list of closest names.
- E-mails and phones are what the employer wrote in the text: a phone number is kept only when the text says it is a phone number.
- The labels (tags, sectors, pay text) are in the language of the country's site, as on Indeed (French on the French site); the pay of a job read by key is only given on the site of the job's own country, so give the country of the keys you read.
- Salaries filtered by `minSalary` / `maxSalary` include the ones Indeed estimates; `salarySource` tells which are estimates.
- `industries` given as names are matched on the employer's sector label, after the page is read; Indeed sector keys are filtered by Indeed.
- `onlyNew` remembers job keys, not their content: a job whose text changed is not returned again.
- Two runs sharing the same `stateKey` at the same time may both return the same new job.

**A run that reaches its timeout** stops itself about 45 seconds before it: no new page is asked, what it read is saved and, with `onlyNew`, remembered, and the run ends *Succeeded* with "Stopped before the run's timeout". Resurrect it to go on from there, or give the next run a longer timeout (Run options).

**A run the platform stops without warning** (out of memory)

- Resurrect it: it goes on from where it stood at most a minute before the stop. What it had read since is read again, and the jobs already saved are skipped: none is delivered or charged twice, and `maxItems` still counts them.
- With `onlyNew`, the memory is saved after each write (at most once every 2 seconds): resurrect the stopped run and the jobs it had saved meanwhile join the memory; leave it stopped for good, and the next run may return the jobs of its last 2 seconds once more.

#### Something doesn't work?

The last line of the log counts the jobs saved and filtered out, and the requests that failed after every retry. Those requests are listed, with the reason, in the `FAILED_REQUESTS` record of the run's key-value store. A run that saved nothing and had failed requests fails, and its last message gives the cause.

If Indeed changes its pages, you are told instead of paying for blank rows. A results page that counts jobs but gives none the Actor can read is an error (listed in `FAILED_REQUESTS`), never a quiet "No jobs found". If the first 20 jobs read all lack their title, date, company name or location, the run saves nothing more, stops and fails, and its last message names the missing field: at most those first jobs are charged. A job that `postedAfter` / `postedBefore` drops because it has no date at all counts among those 20. A run of fewer jobs (a scheduled `onlyNew` run that finds a few new ones, a small **Max jobs**) is checked at its end, from 5 jobs: its jobs are already saved, and the run fails with the missing field instead of ending green.

### 🛟 Support

Open an issue in the **Issues** tab with a link to your run: the run log and the `FAILED_REQUESTS` record of the key-value store show exactly which requests failed and why.

# Actor input Schema

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

Indeed URLs from any country site: search results (`https://www.indeed.com/jobs?q=nurse&l=Texas`, keyword, location, radius, date, sort and job-type filters are kept), company job pages (`https://www.indeed.com/cmp/Hubspot/jobs`; its own keyword and place are kept: `https://www.indeed.com/cmp/Hubspot/jobs?q=engineer&l=Boston`) or single jobs (`https://www.indeed.com/viewjob?jk=4f9a1c2b3d4e5f60`). When this list is not empty, the search fields below (keywords, locations, countries, companies) are ignored; filters, caps and monitoring still apply. For a single job link, the date, job-type, work-arrangement, experience, pay and language filters are checked on the job itself (a job that does not show the criterion is dropped, and counted as filtered out); the radius and the location of a search cannot be read on a job and are not applied. Max 1 000 URLs.

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

Job title, skill or company, as typed on Indeed (e.g. `software engineer`). Indeed search syntax works: `title:(nurse)`, `"exact phrase"`, `-excluded`, `or`. Empty = every job of the location.

## `searchQueries` (type: `array`):

Several keywords in one run: one search per keyword (times each location and each country). Added to **Search keyword**; jobs found by several searches are saved once.

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

City, state, region or postal code, as typed on Indeed (e.g. `New York, NY`, `Paris`, `Remote`). Empty = the whole country.

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

Several locations in one run: every keyword is searched in every location (3 keywords × 4 locations = 12 searches per country, max 500 searches). Added to **Location**.

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

Indeed country site to search (each country has its own jobs). With no keyword, no location, no URL, no job key and no company: the default (United States) alone runs a small example (20 jobs), any other country reads every job of that country (up to **Max jobs**). The labels (tags, sectors, pay text) come in the language of the country's site, as on Indeed.

## `countries` (type: `array`):

Search several Indeed country sites in one run: every keyword × location is searched in each of them. With **Country** left on United States, only the countries of this list are searched (to include the US, put `US` here); another **Country** is added to the list. Leave **Location** empty to cover each whole country. With no keyword or location, a filled list alone reads every job of those countries (the default country is then not added: for the whole US, put `US` here).

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

All the open jobs of these employers: company names (`Starbucks`), website domains (`starbucks.com`), Indeed company pages (`https://www.indeed.com/cmp/Starbucks`) or employer keys. A name or domain is matched to the exact Indeed employer (a domain must match the employer's website), not to look-alike names. Combined with the keywords and locations above when they are filled.

## `jobKeys` (type: `array`):

Indeed job keys (the jk= value of a job link) to read directly, up to 10 000. They are read on top of the keyword and location searches: empty those fields to read only these jobs. Expired jobs are returned too, flagged as expired. The date, job-type, work-arrangement, experience, pay and language filters are checked on each job (a job that does not show the criterion is dropped, and counted as filtered out); the radius and the location cannot be read on a job and are not applied. A job link is read on the site of the link, a bare key on the **Country** site — on the first of **Countries** when Country is left on United States and that list is filled (give the country of the job to get its pay).

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

Maximum number of jobs to save for the whole run (after deduplication and filters). 0 = no limit.

## `maxItemsPerQuery` (type: `integer`):

Cap for EACH search (keyword × location × country, company, or search URL), so that the first search cannot use up the whole **Max jobs** budget. 0 = no per-search cap.

## `splitLargeSearches` (type: `boolean`):

Indeed shows at most 1,000 jobs per search. When a search has more, the Actor splits it into publication-date slices and collects each one (measured: 99.9 % of searches of 2,500 to 13,700 jobs instead of the first 1,000). The log and the `COVERAGE` record give announced vs collected jobs per search. Off = first 1,000 only.

## `descriptionFormat` (type: `string`):

Which forms of the full job description to save. Smaller = lighter dataset.

## `extractContacts` (type: `boolean`):

Add the e-mail addresses and phone numbers the employer wrote in the job description (`emails`, `phones`). Charged only for jobs where at least one is found (on 700 jobs read in the US, France and Germany: 19 % had an e-mail, 8 % a phone number). See Pricing.

## `companySummary` (type: `boolean`):

Also save one row per employer found (`recordType` = `company`): profile, total open jobs on Indeed, new jobs in the last 24 hours / 7 days / 30 days, and the change since the previous run with the same **Memory key**. Ready for a CRM or n8n. Charged per company row. See Pricing.

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

Only jobs added to Indeed in this period.

## `jobTypes` (type: `array`):

Keep jobs of ANY of these types. Empty = all. Outside the USA, the fixed-term contract Indeed shows as "Contract" (CDD, befristet…) is Fixed-term contract; Contract is the contractor, mostly USA, Canada and Australia.

## `remoteType` (type: `string`):

Work arrangement as tagged by Indeed.

## `experienceLevels` (type: `array`):

Keep jobs of ANY of these levels, as tagged by Indeed. Empty = all.

## `industries` (type: `array`):

Keep jobs of employers in ANY of these sectors, as named by Indeed in the country's language (e.g. `Healthcare`, `Information Technology`, `Banking`) — matched on the employer's sector — or by Indeed sector key (e.g. `22VKN`), which Indeed itself filters on. Use either names or keys in one run, not both. Empty = all.

## `minSalary` (type: `integer`):

Jobs whose yearly pay range reaches this amount, in the country's currency (Indeed's own filter: includes salaries estimated by Indeed). 0 = no filter.

## `maxSalary` (type: `integer`):

Jobs whose yearly pay range starts below this amount, in the country's currency (Indeed's own filter, like Minimum yearly salary). 0 = no limit.

## `radius` (type: `integer`):

Distance around **Location**. 0 = exact place only. Empty = Indeed's default (25 miles).

## `radiusUnit` (type: `string`):

Unit of **Radius**.

## `jobLanguage` (type: `string`):

Only jobs written in this language: two-letter code such as `en`, `fr`, `de`. Empty = all.

## `sortBy` (type: `string`):

Order of the results (matters with **Max jobs**). With **Only new jobs** or **Posted after**, the results are always read newest first.

## `postedAfter` (type: `string`):

Only jobs added to Indeed on or after this date (`dateOnIndeed`): `2026-09-01` (a date alone starts at 00:00 UTC), or a period before now such as `7 days`, `2 weeks`, `1 month` (API: `24 hours` and full ISO date-times work too). Also sent to Indeed to read fewer pages.

## `postedBefore` (type: `string`):

Only jobs added to Indeed on or before this date (the whole day is included, in UTC), or older than a period such as `30 days`.

## `excludeKeywords` (type: `array`):

Drop the jobs whose title, or the cleaned-up title Indeed shows (`displayTitle`), contains one of these words (case and accents ignored).

## `excludeCompanies` (type: `array`):

Drop the jobs of these employers (the company name as shown, its Indeed company page name `companyProfileName`, or its parent group `parentCompanyName`, contains one of these words, case and accents ignored), e.g. staffing agencies.

## `onlyWithSalary` (type: `boolean`):

Drop the jobs that show no pay.

## `applyMethod` (type: `string`):

Indeed Apply = jobs candidates apply to on Indeed (the isEasyApply column); Employer site = jobs that send candidates to the employer's or ATS page (the applyUrl column).

## `minCompanyRating` (type: `number`):

Drop the jobs of employers rated below this on Indeed (out of 5). Jobs of unrated employers are dropped too when set. 0 = no filter.

## `onlyNew` (type: `boolean`):

Skip the jobs that a previous run (same **Memory key**) already delivered: they are not saved and not charged. First run = everything is new.

## `stateKey` (type: `string`):

Name of the memory used by **Only new jobs** and by the company change counts. Give each schedule / task its own key (e.g. `nurses-texas`) so that they do not share their memory. Letters, digits, `-` and `_`.

## `resetState` (type: `boolean`):

Forget everything remembered under this **Memory key** before the run: this run returns (and charges) every job again. Untick it afterwards.

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

Apify Proxy or your own proxies. Keep the default: it is included in the price. The residential Apify proxy is not available in this Actor.

## `maxConcurrency` (type: `integer`):

Maximum number of requests processed in parallel. Lowered by itself to what the memory of the run holds (5 at the default 512 MB, 17 at 1 GB): give the Actor more memory to read more at a time.

## `maxRequestsPerMinute` (type: `integer`):

Most requests to Indeed in any 60 seconds (each request returns up to 100 jobs). A budget, not an even pace: up to this many can leave at once when the minute starts (spread them with the minimum delay below). Lower it if the log shows HTTP 429 / 403.

## `minRequestIntervalMs` (type: `integer`):

Smallest gap between two requests, in milliseconds. Unlike the per-minute rate, this spreads the requests evenly instead of letting them go out in a burst. 0 = no gap.

## `maxRequestRetries` (type: `integer`):

Retries per request before it is marked as failed. Behind a proxy, a request the site turns away is also retried on a new proxy session up to 10 times without using up these retries.

## `debugLog` (type: `boolean`):

Include debug messages in the run log.

## Actor input object example

```json
{
  "startUrls": [],
  "query": "software engineer",
  "searchQueries": [],
  "location": "New York, NY",
  "locations": [],
  "country": "US",
  "countries": [],
  "companies": [],
  "jobKeys": [],
  "maxItems": 100,
  "maxItemsPerQuery": 0,
  "splitLargeSearches": true,
  "descriptionFormat": "text_html",
  "extractContacts": false,
  "companySummary": false,
  "datePosted": "any",
  "jobTypes": [],
  "remoteType": "any",
  "experienceLevels": [],
  "industries": [],
  "minSalary": 0,
  "maxSalary": 0,
  "radiusUnit": "miles",
  "sortBy": "relevance",
  "excludeKeywords": [],
  "excludeCompanies": [],
  "onlyWithSalary": false,
  "applyMethod": "any",
  "minCompanyRating": 0,
  "onlyNew": false,
  "stateKey": "default",
  "resetState": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 3,
  "maxRequestsPerMinute": 60,
  "minRequestIntervalMs": 0,
  "maxRequestRetries": 5,
  "debugLog": false
}
```

# Actor output Schema

## `results` (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 = {
    "query": "software engineer",
    "location": "New York, NY",
    "country": "US",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("nice_dev/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",
    "country": "US",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("nice_dev/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",
  "country": "US",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call nice_dev/indeed-jobs-scraper --silent --output-dataset

```

## MCP server setup

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