# Indeed Jobs Scraper — Salaries, Benefits & Company Data (`spool/indeed-scraper`) Actor

Indeed jobs scraper and API: every posting with its full description, the employer's salary and Indeed's estimate in USD, benefits and schedule, company industry, size, revenue, CEO and website. 74 countries, pasted Indeed URLs, filters that never bill, a new-jobs monitor. $3 per 1,000 jobs.

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

## Pricing

from $3.00 / 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 and API: every posting with its full description, the employer's pay annualised in USD, benefits,
schedule and skills as Indeed tags them, and the company's industry, size, revenue, CEO and website, from any of
Indeed's 74 country sites.** Rows are deduplicated, located and classified (department, seniority, job category,
employment type, years of experience, visa sponsorship, education, 190 technologies), filtered before anything is
charged, and delivered page by page while the next page is still loading.

```
Data Engineer                        FanDuel                      $116,000–$145,000 /yr   hybrid   Commuter assistance · 401(k)        via FanDuel
Corporate Accountant                 Lancashire County Council    £42,123–£47,665 /yr     onsite   Company pension · Employee discount via the council's site
Contact Center Representative        Aloha Pacific FCU            $18.00–$23.98 /hr       remote   Health insurance · Paid holidays    via Aloha Pacific FCU
```

**What a run costs.** $0.003 per job row, so **1,000 jobs are $3**, the same on every plan. The default run below asks
for 200 jobs and costs about **$0.60**. Duplicates, rows a filter removed and empty pages cost nothing.

**Try it in one minute**

1. Press **Try for free** and keep the prefilled input, or paste this:
   ```json
   { "searchTerms": ["data analyst"], "location": "Chicago, IL" }
   ```
2. Press **Start**. Indeed is read 100 postings at a time through its own JSON API, each page is enriched and delivered as it lands.
3. Open the **Output** tab: the *Jobs*, *Salaries*, *Benefits and schedule*, *Companies* and *Technologies* views each show their own columns. Export as JSON, CSV or Excel, or read the dataset from the API.

Measured on the platform, 31 October 2026, build 1.0, four runs started in the same minute: the default search
(`software engineer`, whole United States, 200 rows) delivered 200 jobs in 4.8 seconds, first rows at 2.2 seconds,
every row with a description, 97 with the employer's pay; `data engineer` in New York, 200 rows in 32 seconds, 187
with pay; `customer service representative`, remote, employer pay required: 200 rows in 59 seconds, 400 postings
read, the 159 without pay removed before billing; `accountant` in Manchester on uk.indeed.com, 100 rows in 7 seconds.

### Why this one

| | This Actor | A typical Indeed scraper |
|---|---|---|
| Source | Indeed's own JSON API, the one its mobile app uses, 100 postings per request with the full description | HTML pages, 15 per page, the description fetched one by one |
| Pay | the employer's stated pay as min, max, interval and currency, annualised and in USD; ranges written only in the text read out too; the recruiter's own pay text kept | one salary string, or none |
| Benefits, schedule, skills, workplace type | every attribute Indeed shows, sorted into benefits, schedule, skills and pay types | not delivered |
| Company | industry, size, revenue, CEO, website, logo, header image, address, description, Indeed company page | name and maybe a logo |
| Where the posting came from | `postedVia`: the employer's site, an ATS or a board | not delivered |
| Classification | department, seniority, job category, employment type, years of experience, visa sponsorship, education, 190 technologies | none |
| Filters | 14 filters applied before billing; removed rows cost nothing | board-side filters only |
| Countries | 74 Indeed sites, by name or from a pasted URL | often US only |
| Pasted Indeed URLs | yes, any country site; keywords, place, radius, date, remote, job type and level read from the URL | sometimes |
| Only what is new | monitor mode remembers what a search already delivered | no |
| Delivery | page by page, first rows in seconds | all at the end |

### What you get

Every row carries the job, the pay, the employer and the classification:

- **The job**: `title`, `company`, `location` (with `city`, `region`, `country`, `postalCode`, `streetAddress` where Indeed has them), `workplaceType` (remote, hybrid, onsite), `jobType` and `jobTypes`, `jobLevel` when Indeed tags one (entry, mid, senior), `datePosted` (the employer's publish date) and `dateOnIndeed` (the day it reached Indeed), `postedDaysAgo`, `jobUrl` on Indeed, `jobUrlDirect` on the employer's site when the posting lives there, `applyUrl`, `postedVia` (the employer's site, an ATS, or Indeed itself), the full `description` in Markdown, HTML or plain text.
- **The pay**: `salaryMin`, `salaryMax`, `salaryInterval`, `salaryCurrency`, `salarySource` (employer, or description when read from the text), `salaryMinAnnual`/`salaryMaxAnnual`, `salaryMinUsd`/`salaryMaxUsd`, `salaryText` as the recruiter wrote it, `payTypes` (bonus pay, commission, tips, holiday pay). Indeed's own estimate lands in `estimatedSalary*` with `hasEstimatedSalary` when the API returns one, which in our measurements it rarely does.
- **Benefits, schedule, skills**: `attributes` (every label Indeed shows), `benefits`, `schedule`, `skills` (the requirement labels Indeed lists alongside), `workSchedule`.
- **The employer**: `companyUrl` (Indeed company page), `companyWebsite`, `companyIndustry`, `companyNumEmployees`, `companyRevenue`, `companyCeo`, `companyLogo`, `companyHeaderImage`, `companyDescription`, `companyAddresses`.
- **Classification**: `department`, `seniority`, `jobCategory`, `employmentType`, `yearsExperienceMin`/`Max`, `visaSponsorship`, `educationLevel`, `tech`, `techCategories`, `emails` found in the text.
- **Bookkeeping**: `searchTerm` (which of your terms found it), `matchedLocation`, `sourceSearchUrl`, `dedupeKey`, `firstSeenAt` (monitor mode), `keywordMatchPercent` when you score against keywords.

### Salaries you can compare

The employer's stated pay goes to `salary*` with `salarySource: "employer"`, as min, max, interval and currency, then
annualised (hourly x 2,080, daily x 260, weekly x 52, monthly x 12) and converted to USD with the day's rates, so a
Berlin posting in EUR and a Toronto one in CAD sort on one column. When the employer states nothing in the pay fields
but writes a range in the text, the range is read out and marked `salarySource: "description"`. `salaryText` keeps
the pay exactly as the recruiter wrote it. Indeed's own estimate, the one its site shows under "Estimated", is a
separate thing: when Indeed's API returns it, it lands in `estimatedSalary*` and never in the employer's fields; in
our measurements the API returned it for none of the postings read, so count on the employer's pay, which 50 to 95
percent of postings state depending on the trade. `requireSalary` keeps only employer-stated pay.

### Benefits, schedule, workplace

Indeed tags each posting with attribute labels: Health insurance, 401(k), Paid time off, Monday to Friday, Day shift,
Weekends as needed, Cash register, Forklift, Bilingual, Hybrid work, Remote, In person, Entry level. All of them are in
`attributes`; the ones that are benefits are in `benefits`, the ones about days and shifts in `schedule`, the pay
types (bonus, commission, tips) in `payTypes`, the requirement labels in `skills`, the level in `jobLevel`, and
`workplaceType` says remote, hybrid or onsite. `remoteOnly` uses Indeed's own remote filter first, then every row is judged again by its location, attributes
and text; a row with no remote evidence is removed before billing.

### 74 countries

`country` picks the Indeed site: usa (default), canada, uk, ireland, australia, new zealand, india, singapore, hong kong,
japan, south korea, taiwan, philippines, malaysia, indonesia, thailand, vietnam, pakistan, bangladesh, germany, france,
netherlands, belgium, luxembourg, switzerland, austria, italy, spain, portugal, denmark, sweden, norway, finland,
poland, czech republic, slovakia, hungary, romania, bulgaria, croatia, slovenia, greece, cyprus, malta, estonia,
latvia, lithuania, ukraine, turkey, israel, united arab emirates, saudi arabia, qatar, kuwait, bahrain, oman, egypt,
morocco, nigeria, south africa, brazil, mexico, argentina, chile, colombia, peru, ecuador, venezuela, uruguay, costa
rica, panama, china. Results come in the site's language; descriptions are delivered as published.

### Paste a search URL

Copy the address bar from any Indeed search and put it in `searchUrls`. Keywords, place, radius, the date window
(`fromage`), remote, job type and experience level are read from it, and the country from the host
(`uk.indeed.com`, `de.indeed.com`). One search per URL, up to 20; every row says which URL found it in `sourceSearchUrl`.

### Filters that never bill

Indeed applies its own date window, remote, job type and Indeed Apply filters before anything is read. Everything
else is applied here, after the rows are read and before anything is charged. A row with no evidence for a filter (no
date, no seniority cue) is kept; silence is not a mismatch. `RUN_SUMMARY.filterRemoved` shows the count per filter.

| Filter | Keeps |
|---|---|
| `requireSalary` | employer-stated pay only (Indeed's estimate does not count) |
| `minSalaryUsd` | annualised pay at or above the number |
| `requireTech` | postings that mention any of the technologies |
| `excludeTitleKeywords` / `requireTitleKeywords` | by words in the title |
| `excludeCompanies` / `targetCompanies` | by employer, parent names included |
| `excludeCities` | drops places you do not want inside a wide radius |
| `excludeAgencies` | drops staffing and recruiting agencies |
| `maxAgeDays` | by the employer's publish date |
| `experienceLevel` | junior, mid, senior, lead, as classified from the title |
| `strictKeywordMatch` | only postings whose title or text carries every word of your term |
| `remoteOnly` | rows with remote evidence (Indeed's filter, then location, attributes, text) |
| `jobType` | Indeed's job type, checked again against the posting |

### Monitor mode: only what is new

`onlyNew: true` remembers what a search already delivered, in a key-value store in your own account, and later runs
deliver only postings not seen before. Schedule the run daily and the dataset is your feed of new Indeed jobs. Rows
seen before are not delivered and not charged. `monitorName` names the memory when you run several monitors.

### Alerts

`webhookUrl` receives a digest when a run delivers rows: how many, the top titles and companies, the salary range,
links to the dataset. A Slack or Discord webhook gets a readable message; any other URL gets JSON.

### Rows arrive page by page

Indeed answers 100 postings per request. Each page is enriched and delivered as it lands, so the first rows are in
the dataset within seconds and `RUN_SUMMARY.waves` says when each page arrived. Several terms and places are paged at
the same time, each until its share of `results` is met; when the filters leave the run short, the searches that still
have pages take turns until the target, and, if your own words run dry, the same role is searched under its other
names (a row found that way must carry the related title in its own title; `expandTerms: false` turns this off).

### Every option

| Option | Default | What it does |
|---|---|---|
| `searchTerms` | | job titles or keywords, one search each (up to 10) |
| `location` | | city, state, postal code or country as typed on Indeed; empty = the whole country |
| `locations` | | more places; every term is searched in every place (up to 10 places) |
| `country` | `usa` | which Indeed site, by name (74) |
| `searchUrls` | | pasted Indeed search URLs; when given, the fields above are ignored |
| `results` | `200` | jobs to deliver per run, after deduplication and filters (1 to 10,000) |
| `postedWithinHours` | | Indeed's date window: 24, 72, 168 ... |
| `remoteOnly` | `false` | Indeed's remote filter plus the evidence check |
| `jobType` | | fulltime, parttime, contract, temporary, internship, perdiem, volunteer |
| `indeedApplyOnly` | `false` | only postings applied to through Indeed |
| `radiusMiles` | `50` | distance around the place |
| `includeEstimatedSalary` | `true` | deliver Indeed's estimate in the estimatedSalary\* fields when the API returns one |
| `descriptionFormat` | `markdown` | markdown, html or text |
| `requireSalary`, `minSalaryUsd`, `requireTech`, `excludeTitleKeywords`, `requireTitleKeywords`, `excludeCompanies`, `targetCompanies`, `excludeCities`, `excludeAgencies`, `maxAgeDays`, `experienceLevel`, `strictKeywordMatch` | | the filters above |
| `resumeKeywords` | | score every row against these words (`matchedKeywords`, `keywordMatchPercent`) |
| `sortBy` | | `salary_desc` or `date_desc` inside each delivered page |
| `expandTerms` | `true` | search related titles when your words leave the target short |
| `onlyNew`, `monitorName` | `false` | monitor mode |
| `webhookUrl` | | alert digest |
| `useApifyProxy`, `proxyConfiguration` | Apify residential | Indeed refuses Apify's own addresses, so residential sessions are used |

### How much does it cost?

$0.003 per job row delivered, on every plan. Nothing else is charged except Apify's standard Actor start.

| Run | Rows | Price |
|---|---|---|
| Default (200 rows) | 200 | $0.60 |
| A daily monitor that finds 30 new postings | 30 | $0.09 |
| A 1,000-row sweep | 1,000 | $3.00 |
| A salary study, 150 rows that state pay | 150 | $0.45, the rows without pay were never charged |

Compute and proxy traffic are covered by the row price. Set a maximum total charge on the run if you want a hard cap;
rows beyond it are not delivered and not charged, and a notice row says how many were held back.

### Use it from an AI agent, the API or an integration

The Actor is listed for agentic payments and works as an MCP tool through Apify's MCP server. From code:

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("spool/indeed-scraper").call(run_input={"searchTerms": ["data engineer"], "location": "New York, NY", "results": 300})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["title"], job["company"], job["salaryMinUsd"], job["applyUrl"])
```

```js
const { ApifyClient } = require('apify-client');
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('spool/indeed-scraper').call({ searchTerms: ['data engineer'], location: 'New York, NY' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Schedule it, connect the dataset to Google Sheets, Make, Zapier or n8n through Apify's integrations, or point the
alert webhook at Slack.

### What it covers, honestly

- **Indeed's matching is Indeed's.** A search for "electrician" returns what Indeed ranks for it, including adjacent trades. `strictKeywordMatch` keeps only rows whose title or text carries your words; `requireTitleKeywords` narrows to the title.
- **Two dates.** `datePosted` is the employer's publish date; `dateOnIndeed` is the day the posting reached Indeed, which is what `postedWithinHours` filters on. They can differ by days for postings Indeed picks up from employer sites.
- **Indeed's pay estimate is rarely in this API's answer.** The fields exist and fill when it is; do not plan on them. The employer's stated pay is what you get, on 50 to 95 percent of postings depending on the trade.
- **Company rating and review counts** are not part of this API answer and are not delivered.
- **Remote evidence.** On remote searches, rows that carry no remote cue in their location, attributes or text are removed before billing. Indeed's remote tag counts as a cue.
- **Deep runs** page 100 at a time; a 1,000-row search is ten requests per term. Very broad searches with heavy filters read more pages than they deliver; `RUN_SUMMARY.postingsScanned` says how many.
- **Pay ranges written in the text** are read by rules and marked `salarySource: "description"`; check them before you rely on them.

### Is this legal?

The Actor reads publicly listed job postings that Indeed shows to anyone, without logging in, through the same API
Indeed's app uses, paced through residential sessions. It stores what the posting says. Email addresses that appear
in a posting's text are delivered as published; handle them under the law that applies to you. You are responsible
for how you use the data, including Indeed's terms of use and the data protection rules of your jurisdiction.

### FAQ

**Why do I see titles I did not type?** Indeed's own matching, or, when your words left the target short, related
titles of the same role. `searchTerm` on every row says which term found it; `strictKeywordMatch` or
`expandTerms: false` narrows it.

**Why is the salary empty when Indeed's site shows one?** What the site shows is Indeed's estimate, which its API
rarely returns; the `salary*` fields carry what the employer stated or wrote in the text, and `salaryText` the
recruiter's own words.

**Can I get more than 1,000 rows?** Yes, `results` goes to 10,000. Indeed returns at most about 1,000 postings per
search, so use several terms or places for more.

**Does it work outside the US?** Yes, 74 country sites by name or pasted URL. Salaries are converted to USD at the
day's rates; the original currency is kept.

**How fast is it?** The first page lands in a few seconds; a 200-row run finishes in well under a minute; 1,000 rows
in a couple of minutes. `RUN_SUMMARY.waves` shows when each page arrived.

**What if a run hits its time limit?** It stops itself a margin before the limit, delivers what it had, writes its
summary and ends as succeeded with `status: stopped-at-time-limit`.

### Changelog

**1.0.0 — 2026-10-31.** First release. Indeed through its own JSON API, 100 postings per request with full
descriptions; employer pay annualised in USD, pay ranges read from the text, the recruiter's pay text kept; benefits,
schedule, skills, pay types, level and workplace type from Indeed's attributes; company industry, size, revenue, CEO,
website, logo and header image; where the posting came from; 74 countries; pasted search URLs; 14 filters that never
bill; monitor mode; alert webhook; related-title fill with the title rule; page-by-page delivery. Measured: 200 rows
in 4.8 s (default), 200 filtered remote rows with pay in 59 s, 100 UK rows in 7 s.

### Support

Issues tab on this page, or the email on the spool profile. Bugs are fixed in days, with the fix in the changelog.

### More from spool

- **Job Board Scraper & API**: one search across LinkedIn, Indeed, Glassdoor and 30 more sources, the employer's own posting attached.
- **Job Search Scraper**: live jobs straight from company career pages, 1,100 verified employer boards.
- **Career Page Scraper & ATS Jobs API**: Greenhouse, Lever, Ashby, Workday, SmartRecruiters and 12 more ATS platforms, no proxy.

# Actor input Schema

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

One search per term. Indeed's own matching: a term finds postings whose title or text matches it. Up to 10 terms.

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

City, state, postal code or country as you would type it on Indeed. Leave empty for the whole country.

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

Each term is searched in each of these places too (up to 10 places in all).

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

Which Indeed site: usa, uk, canada, australia, germany, france, india, netherlands, spain, italy, brazil, mexico, singapore, uae, south africa ... 74 countries. Pasted URLs carry their own country.

## `searchUrls` (type: `array`):

Indeed search URLs copied from the browser (any country site). Keywords, place, radius, date window, remote, job type and experience level are read from the URL; one search per URL, up to 20. When given, the fields above are ignored.

## `results` (type: `integer`):

Jobs to deliver per run, across all searches, after deduplication and filters. Up to 10,000. A search is paged 100 at a time until the target is met; rows a filter removes are never charged.

## `postedWithinHours` (type: `integer`):

Only postings that reached Indeed in the last N hours (24 = today, 72 = three days, 168 = a week). Indeed applies this itself.

## `remoteOnly` (type: `boolean`):

Indeed's remote filter, then every row is judged again by its location, attributes and text; rows without remote evidence are removed before billing.

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

Indeed's own job-type filter.

## `indeedApplyOnly` (type: `boolean`):

Only postings that take applications through Indeed itself.

## `radiusMiles` (type: `integer`):

Distance around the place, in miles (Indeed's unit everywhere).

## `includeEstimatedSalary` (type: `boolean`):

Indeed's own pay estimate, when its API returns one (measured: rarely; the fields are null otherwise). Delivered in the estimatedSalary\* fields, never mixed into the employer's salary fields, and not counted by the salary filter.

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

How the full description is delivered. Every Indeed row carries its description; no extra request is made.

## `requireSalary` (type: `boolean`):

Only postings where the employer states pay (Indeed's estimate does not count). Removed rows are never charged.

## `minSalaryUsd` (type: `integer`):

Compared against the annualised USD figure, so an hourly US wage and a monthly EUR salary are judged on the same scale.

## `requireTech` (type: `array`):

Keep rows whose text names any of these — `Kubernetes`, `Snowflake`, `Salesforce`, `React`… Spelled as in the output's `tech` field. 190 technologies are recognised.

## `excludeTitleKeywords` (type: `array`):

Drop jobs whose title contains any of these words or phrases (case-insensitive). Removed rows are never charged.

## `requireTitleKeywords` (type: `array`):

Keep only jobs whose title contains at least one of these words or phrases (case-insensitive, title only). The answer to a board that matches your term against descriptions. Removed rows are never charged.

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

Drop these employers. Names are normalised first, so `Wipro` also drops `Wipro Limited`. Removed rows are never charged.

## `targetCompanies` (type: `array`):

Keep only these employers (competitor hiring watch). Same normalisation plus a parent match: `Amazon` keeps `Amazon Web Services`, not `Amazonia`. Removed rows are never charged.

## `excludeCities` (type: `array`):

Drop jobs whose location mentions any of these places (whole word, case-insensitive). Removed rows are never charged.

## `excludeAgencies` (type: `boolean`):

Drop rows whose company name reads like a staffing or recruiting agency (staffing, recruit…, headhunt…, talent…, manpower, personnel, placement…, consultanc…, resourcing, workforce…). A heuristic on the name only. Removed rows are never charged.

## `maxAgeDays` (type: `integer`):

Drop postings older than N days by the employer's publish date (Indeed's own window above counts the day the posting reached Indeed). Rows without a date are kept.

## `experienceLevel` (type: `array`):

Keep only these levels, read from the board's own label where it has one, else from the title (Senior, Principal, Lead; Junior, Graduate, Entry level; Intern; Director; VP, Chief, Head of). A row with no level cue is kept, never dropped. Removed rows are never charged.

## `strictKeywordMatch` (type: `boolean`):

Keep only rows whose title or description contains every word of one of your search terms. Off by default: LinkedIn never answers "nothing matched" and returns loosely related postings instead. Removed rows are never charged.

## `resumeKeywords` (type: `array`):

Your skills (`Python`, `Kubernetes`, `C++`, `.NET`). Every row gets `matchedKeywords` and `keywordMatchPercent` against them; without this list the same two fields score the row against the run's own search terms. Annotation only: nothing is removed, nothing changes the bill.

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

Order of the rows inside each delivered page: Indeed's relevance (default), highest salary first, or newest first.

## `expandTerms` (type: `boolean`):

When your own words leave the target short, the same role is also searched under its other names (electrician: journeyman electrician, electrical technician); a row found that way must carry the related title in its own title. searchTerm on every row says which term found it.

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

Remember every posting this search has delivered and return only the ones not seen before. Put the run on a schedule — daily, hourly — and you get a feed of new postings; nothing delivered earlier is charged again. The memory is a record per search in a key-value store named `job-board-api-monitor` in **your** account (see *Monitor name*). Postings not seen for 60 days are forgotten, so a role closed and reposted later shows up again.

## `monitorName` (type: `string`):

Name of the memory record for this search. Leave empty and it is derived from the search itself (terms, location, sites, companies), so the same search always shares one memory. Set a name to share one memory between different searches, or change it to start fresh. It is a label, not a credential — it appears in your key-value store as the record key.

## `webhookUrl` (type: `string`):

When rows are delivered, POST a digest here: a Slack or Discord incoming webhook gets a short message with the first rows and a link to all of them; any other URL gets the full JSON digest (run and dataset ids and links, counts, the first ten rows). A run that delivers nothing sends nothing, so a scheduled monitor only speaks when there is something new.

## `useApifyProxy` (type: `boolean`):

Indeed refuses requests from Apify's own addresses, so the run uses residential proxy sessions. Turn off only if you supply your own proxies below.

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

Your own proxy settings, if any. Default: Apify Proxy, residential group, eight sessions named per run.

## Actor input object example

```json
{
  "searchTerms": [
    "data engineer",
    "analytics engineer"
  ],
  "location": "Chicago, IL",
  "locations": [
    "San Francisco, CA",
    "Austin, TX",
    "Seattle, WA"
  ],
  "country": "usa",
  "searchUrls": [
    "https://www.indeed.com/jobs?q=python+developer&l=Austin%2C+TX&fromage=14",
    "https://www.linkedin.com/jobs/search?keywords=python%20developer&location=Austin%2C%20Texas&f_TPR=r604800"
  ],
  "results": 200,
  "remoteOnly": false,
  "jobType": "",
  "indeedApplyOnly": false,
  "radiusMiles": 50,
  "includeEstimatedSalary": true,
  "descriptionFormat": "markdown",
  "requireSalary": false,
  "excludeTitleKeywords": [
    "senior",
    "intern"
  ],
  "requireTitleKeywords": [
    "engineer"
  ],
  "excludeAgencies": false,
  "strictKeywordMatch": false,
  "sortBy": "",
  "expandTerms": true,
  "onlyNew": false,
  "webhookUrl": "https://hooks.slack.com/services/…",
  "useApifyProxy": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

Job rows (the `jobs` view).

## `salaries` (type: `string`):

Pay as stated, annualised, in USD.

## `companies` (type: `string`):

Employer details from Indeed's company pages.

## `summary` (type: `string`):

RUN_SUMMARY: pages read, postings scanned, filters, timings.

# 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": [
        "data analyst"
    ],
    "location": "Chicago, IL",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("spool/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": ["data analyst"],
    "location": "Chicago, IL",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("spool/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": [
    "data analyst"
  ],
  "location": "Chicago, IL",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call spool/indeed-scraper --silent --output-dataset

```

## MCP server setup

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