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

Indeed jobs with more salaries filled in: when Indeed leaves pay blank, we recover it from the job text and label where every salary came from. Plus company size, industry, logo and apply link. 62 countries, up to 1,000 jobs per search.

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

## Pricing

from $0.04 / 1,000 job founds

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 Jobs Scraper

Get job listings from Indeed in 62 countries, with the salary as clean numbers you can sort and filter: minimum, maximum, currency and pay period, when a supported salary format is available.

Type a job title and a place, press Start, and download the results as Excel, CSV, JSON, JSONL, XML, HTML or an RSS feed. A search for 1,000 jobs usually finishes in under a minute.

**Contents:** [What you get](#what-you-get-for-each-job) · [Salary](#why-the-salary-is-different-here) · [Price](#how-much-does-it-cost) · [How to use it](#how-to-use-it) · [Input](#input-options) · [Output](#output) · [All fields](#all-fields) · [Downloads](#download-your-data) · [Other tools](#use-it-with-other-tools) · [FAQ](#faq)

### What you get for each job

- **The job:** title, full description (plain text and HTML), job type (full-time, part-time, contract…), shift and schedule, occupation, the skills Indeed tags it with, and the date it was posted
- **Benefits:** the benefits Indeed lists, such as health insurance or parental leave, as a clean list
- **Salary:** minimum, maximum, currency, pay period (hour, day, week, month or year), the salary as written, and where it came from
- **Remote, hybrid or in-person:** we keep them apart, so "remote only" really means remote
- **Hiring signals:** whether you can apply straight on Indeed (Easy Apply), and whether Indeed marks the job as urgent or as hiring many people
- **Location:** city, region, postcode, full address, map coordinates (latitude and longitude) and country
- **The company:** name, star rating and number of reviews on Indeed, Indeed company page, logo, website, industry, number of employees, revenue, a short description, CEO name and photo, and social media links, when Indeed has them
- **Links:** the Indeed job page and the link to apply

If Indeed doesn't have a piece of information, that field is left empty. We never fill it with a guess.

Each job appears once. Some employers post the same job many times, once per language for example; you get it once and pay for it once.

### Why the salary is different here

Indeed shows pay in several ways. Sometimes it's a proper number. Often it's only a line of text such as "£30,000 – £35,000 a year" or "From PHP 25,000 a month". Many scrapers give you that text, or leave the salary blank, so you can't sort or average it without cleaning it yourself.

This scraper turns that text into numbers for you. When Indeed has no salary at all, it also reads the job description for a stated salary (for example "salary: R25 000 per month" or "₹6 LPA"). It knows local formats like lakh, naira and South African spacing, and ignores money that isn't pay, such as referral bonuses, allowances and sales targets.

Every salary is labelled in `salary_source` so you know exactly where it came from:

| `salary_source` | Where the salary came from |
| --- | --- |
| `indeed_stated` | The employer's salary, as numbers from Indeed |
| `indeed_estimated` | Indeed's own estimate (also marked `salary_estimated: true`) |
| `indeed_formatted` | Indeed's salary text, turned into numbers by this scraper |
| `indeed_detailed` | Indeed's longer salary text, turned into numbers by this scraper |
| `description_regex` | Found in the job description by this scraper |

If Indeed already gives a number, we always keep Indeed's number.

### How much does it cost?

**$2 per 1,000 jobs.** That's the whole price: no extra charge for computing time or proxies.

| Jobs | Cost |
| ---: | ---: |
| 100 | $0.20 |
| 1,000 | $2.00 |
| 10,000 | $20.00 |

You only pay for jobs that end up in your results. If you switch on "Salary only", the jobs without a salary are dropped and you don't pay for them. Apify's free plan includes $5 of credit every month, which covers about 2,500 jobs.

### How to use it

1. Enter one or more **search queries**, the same words you'd type into Indeed (for example `nurse` or `python developer`). Or paste **Indeed search URLs** straight from your browser.
2. Pick the **country** and, if you like, one or more **locations** such as cities or postcodes.
3. Choose your filters: newest first, posted within days, remote or hybrid, job type, salary only.
4. Press **Start**. When the run finishes, open the **Output** tab to see the jobs as a table, or download them.

Every query is searched in every location, so 3 job titles and 4 cities make 12 searches in one run. A job found by more than one search is only kept, and charged, once.

#### Input options

| Option | API name | What it does |
| --- | --- | --- |
| Search queries | `queries` | Job titles or keywords, as you'd type them on Indeed. Add as many as you like, or leave empty for every job in each location |
| Locations | `locations` | Cities, regions or postcodes. Leave empty to search the whole country |
| Country | `country` | Which Indeed site to search: any of the 62 below. Default `us` |
| Indeed search URLs | `startUrls` | Searches pasted from Indeed. Their keywords, location, radius, date, sort, job type and remote filters are used, and the country comes from the URL |
| Maximum jobs per search | `maxJobs` | 1 to 1,000 for each search. Default 100 |
| Sort by | `sort` | `relevance` (default) or `date` for newest first |
| Posted within days | `datePosted` | Only jobs posted in the last N days |
| Remote or hybrid | `workplaceType` | `any` (default), `remote` for fully remote only, or `hybrid` |
| Job type | `jobTypes` | Any of `fulltime`, `parttime`, `contract`, `temporary`, `internship`, `perdiem`, `volunteer` |
| Salary only | `salaryOnly` | Only jobs that have a salary |
| Radius | `radius` | How far around each location to search, in miles. 0 means that place only. Default 25 |
| Only new jobs | `onlyNewJobs` | Skip jobs an earlier run already gave you, so scheduled runs only return and charge for new ones |
| Memory name for new jobs | `onlyNewJobsKey` | Keep separate memories for separate schedules, e.g. `uk-nurses`. Default `default` |

#### Example input

```json
{
  "queries": ["marketing manager", "marketing executive"],
  "locations": ["Manchester", "Leeds"],
  "country": "uk",
  "sort": "date",
  "datePosted": 7,
  "jobTypes": ["fulltime"],
  "salaryOnly": true,
  "maxJobs": 500
}
```

### Output

Each job is one row. A real example from the Philippines, with the long description and some of the other fields left out:

```json
{
  "title": "Customer Support & Social Media Specialist",
  "company_name": "Phoenix Water Solutions - Ensure Partner",
  "company_page_url": "https://ph.indeed.com/cmp/Phoenix-Water-Solutions---Ensure-Partner",
  "job_location_raw": "Work from Home",
  "country": "Philippines",
  "country_code": "PH",
  "date_posted": "2026-10-03",
  "salary_min": 38400,
  "salary_max": 48000,
  "salary_currency": "PHP",
  "salary_unit": "month",
  "salary_source": "indeed_stated",
  "salary_estimated": false,
  "workplace_type": "Remote",
  "is_remote": true,
  "job_types": ["Full-time"],
  "attributes": [
    { "key": "99B7P", "label": "Customer service" },
    { "key": "CTUST", "label": "Shopify" },
    { "key": "EY33Q", "label": "Health insurance" }
  ],
  "apply_url": "http://ph.indeed.com/job/customer-support-social-media-specialist-bc9287c1fd81f1dc",
  "source_url": "https://ph.indeed.com/viewjob?jk=bc9287c1fd81f1dc",
  "indeed_job_key": "bc9287c1fd81f1dc"
}
```

#### All fields

| Field | What it is |
| --- | --- |
| `title` | Job title |
| `description_text` | Full job description as plain text |
| `description_html` | Full job description with its original formatting |
| `title_normalized` | Indeed's cleaned-up, lower-case version of the title, handy for grouping |
| `job_types` | Full-time, part-time, contract, temporary, etc. |
| `shift_schedule` | Shift and schedule tags, e.g. `Shift work` |
| `work_schedule` | Schedule details written by the employer, when given |
| `benefits` | Benefits Indeed lists, e.g. `Health insurance`, `Parental leave` |
| `occupations` | Indeed's most specific occupation for the job, e.g. `Registered Nurses` |
| `occupation_categories` | All the occupation groups Indeed puts the job in, from broad to specific |
| `attributes` | Every tag Indeed puts on the job (skills, qualifications, benefits and more), with Indeed's key |
| `workplace_type` | `Remote`, `Hybrid`, `In-person`, or empty when not stated |
| `is_remote` | `true` for fully remote jobs |
| `easy_apply` | `true` when you can apply on Indeed without leaving the site |
| `urgent_hire` | `true` when Indeed marks the job as urgently hiring |
| `high_volume_hiring` | `true` when Indeed marks the employer as hiring many people for this job |
| `language` | Language of the job ad, e.g. `en` |
| `expired` | `true` if Indeed says the job has expired |
| `salary_min` / `salary_max` | Lowest and highest pay; “from” leaves the maximum empty and “up to” leaves the minimum empty |
| `salary_currency` | Currency code, e.g. `GBP`, `USD`, `INR` |
| `salary_unit` | `hour`, `day`, `week`, `month` or `year` |
| `salary_text` | The salary as written, when it came from text |
| `salary_source` | Where the salary came from (see the table above) |
| `salary_estimated` | `true` when the salary is Indeed's estimate |
| `date_posted` | Date the job was posted |
| `date_on_indeed` | Date the job was last seen on Indeed |
| `job_location_raw` | Location as Indeed shows it |
| `full_address` | The full address Indeed has for the job |
| `street_address`, `city`, `state`, `postal_code` | Location broken into parts |
| `latitude`, `longitude` | Map coordinates of the job location |
| `country`, `country_code` | Country name and two-letter code |
| `company_name` | Employer |
| `company_rating` | Star rating out of 5 from Indeed company reviews (empty when there are no reviews) |
| `company_review_count` | Number of Indeed reviews of the company |
| `company_parent_name` | Parent organisation, when Indeed lists one |
| `company_page_url` | Company's page on Indeed |
| `company_logo_url`, `company_banner_url` | Company logo and banner images |
| `company_website` | Company's own website |
| `company_industry` | Industry |
| `company_employee_count` | Number of employees, e.g. `1,001 to 5,000` |
| `company_revenue` | Yearly revenue band |
| `company_description` | Short description of the company |
| `company_ceo_name`, `company_ceo_photo_url` | CEO name and photo |
| `company_facebook`, `company_twitter`, `company_instagram` | Company social media pages, when listed on Indeed |
| `company_addresses` | Company addresses Indeed lists |
| `apply_url` | Where to apply (the employer's site or Indeed) |
| `source_url` | The job page on Indeed |
| `indeed_job_key` | Indeed's ID for the job, useful for removing duplicates across runs |
| `indeed_company_key` | Indeed's ID for the company, useful for grouping jobs by employer |
| `source_name` | Who posted the job to Indeed |
| `search_query`, `search_location`, `search_url` | Which of your searches found the job |
| `scraped_at` | When this row was collected |

A `RUN_STATS` record in the run's key-value store shows, for each search, how many jobs it found, plus how many duplicates and already-seen jobs were skipped.

### Download your data

The **Output** tab has three tables: **Jobs** (the overview), **Salaries**, and **Company, benefits and location**. Download any of them, or all fields, as **Excel, CSV, JSON, JSONL, XML, HTML or RSS**. You can also choose which fields to include.

### Use it with other tools

- **Google Sheets, Make, Zapier and n8n:** send new jobs straight into a spreadsheet, a CRM or a Slack channel. See Apify's guides for [Make](https://docs.apify.com/integrations/make), [Zapier](https://docs.apify.com/integrations/zapier) and [n8n](https://docs.apify.com/integrations/n8n), or [all integrations](https://docs.apify.com/integrations).
- **Schedules:** run the same searches every morning and switch on **Only new jobs**, so each run gives you only jobs you haven't seen, and you only pay for those.
- **API:** start runs and download results from your own code with the Apify API or the JavaScript and Python clients. The **API** tab on this page has ready-to-copy examples.
- **AI agents:** AI assistants such as Claude can run this scraper through [Apify's MCP server](https://mcp.apify.com).

### Who uses it

- **Recruiters and agencies** keeping track of who is hiring, for what, and at what pay
- **Salary research and HR teams** comparing pay by job, city or country
- **Job boards and apps** that need a steady feed of current jobs
- **Sales teams** looking for companies that are growing and hiring
- **Researchers** studying job markets across countries

### Countries

Argentina, Australia, Austria, Bahrain, Belgium, Brazil, Canada, Chile, China, Colombia, Costa Rica, Czech Republic, Denmark, Ecuador, Egypt, Finland, France, Germany, Greece, Hong Kong, Hungary, India, Indonesia, Ireland, Israel, Italy, Japan, Kuwait, Luxembourg, Malaysia, Mexico, Morocco, Netherlands, New Zealand, Nigeria, Norway, Oman, Pakistan, Panama, Peru, Philippines, Poland, Portugal, Qatar, Romania, Saudi Arabia, Singapore, South Africa, South Korea, Spain, Sweden, Switzerland, Taiwan, Thailand, Türkiye, Ukraine, United Arab Emirates, United Kingdom, United States, Uruguay, Venezuela and Vietnam.

### FAQ

**Can I get more than 1,000 jobs?**
Indeed stops at about 1,000 jobs for any one search. Add more queries or locations: each one is its own search with its own 1,000. Duplicates are removed within each run, and **Only new jobs** removes them across runs.

**Why do some jobs have no salary?**
Indeed or the job ad may not state pay, or the wording may not be supported by the parser. We leave unrecognised amounts empty. Turn on "Salary only" if you only want jobs with pay.

**Is the salary from the description reliable?**
The parser looks for stated pay and excludes common bonuses, benefits and funding amounts. It can still make mistakes; description matches are labelled `description_regex` so you can review or filter them. Indeed's own salary text can also contain placeholders or incorrect pay periods. Text parsing does not verify the employer's figures.

**Can I search for remote jobs?**
Yes. Set **Remote or hybrid** to Remote only (hybrid jobs are left out) or Hybrid only. `workplace_type` tells you which jobs are remote, hybrid or in-person.

**Can I copy a search from Indeed?**
Yes. Set up the search on Indeed's website, copy the address from your browser and paste it into **Indeed search URLs**.

**Is it legal to scrape Indeed?**
This scraper only collects job listings that anyone can see on Indeed without logging in. Job ads can contain names or contact details, so check that how you use the data follows the rules where you are, such as GDPR in Europe.

**Something's wrong or missing?**
Open an issue on the **Issues** tab. We read every one.

***

This scraper is not made by or connected to Indeed. "Indeed" is a trademark of its owner and is only used here to say what the tool works with.

# Actor input Schema

## `queries` (type: `array`):

Job titles or keywords, as you'd type them on Indeed. Add several to run them all in one go; each one is searched in every location below.

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

Cities, regions or postcodes. Leave empty to search the whole country. Add several to search each one.

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

Which Indeed site to search (62 countries). Pasted Indeed URLs use their own country.

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

Or paste searches straight from Indeed, e.g. https://uk.indeed.com/jobs?q=nurse\&l=Leeds. Keywords, location, radius, date, sort, job type and remote filters are taken from the URL.

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

1 to 1,000 for each query, location or URL. Indeed stops at about 1,000 jobs for any one search.

## `sort` (type: `string`):

Most relevant first, or newest first.

## `datePosted` (type: `integer`):

Only jobs posted in the last N days.

## `workplaceType` (type: `string`):

Only fully remote jobs, or only hybrid jobs. Leave as Any for everything.

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

Only these job types. Leave empty for all.

## `salaryOnly` (type: `boolean`):

Only jobs that have a salary. Jobs without one are dropped and not charged.

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

How far around each location to search. 0 means that place only.

## `onlyNewJobs` (type: `boolean`):

Skip jobs that an earlier run with the same memory name already returned, so scheduled runs only give (and charge for) new jobs. Jobs are remembered for 90 days.

## `onlyNewJobsKey` (type: `string`):

Use a different name for each separate schedule, e.g. uk-nurses. Runs that share a name share their memory.

## Actor input object example

```json
{
  "queries": [
    "software developer"
  ],
  "locations": [],
  "country": "us",
  "maxJobs": 100,
  "sort": "relevance",
  "workplaceType": "any",
  "salaryOnly": false,
  "radius": 25,
  "onlyNewJobs": false,
  "onlyNewJobsKey": "default"
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `runStats` (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 = {
    "queries": [
        "software developer"
    ],
    "locations": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("surfsup/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 = {
    "queries": ["software developer"],
    "locations": [],
}

# Run the Actor and wait for it to finish
run = client.actor("surfsup/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 '{
  "queries": [
    "software developer"
  ],
  "locations": []
}' |
apify call surfsup/indeed-jobs-scraper --silent --output-dataset

```

## MCP server setup

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