Skip to content

How to Scrape Indeed Job Listings: Salaries, Companies and Apply Links

Search Indeed by job title and city, or paste a company's jobs page, and get each listing as a row with salary, job type, company rating, full description and the employer's apply link. A real run, its cost, and code for curl, Python, Node.js and AI agents.

Published
Reading time
9 min read
Pixel-art robot with an orange >_ on its terminal head takes a job card with a briefcase icon off a cork job board topped with the blue Indeed logo and drops it into a crate
In this article
  1. What data you can get from Indeed
  2. How to scrape Indeed jobs without code
  3. How to scrape Indeed with the API
  4. A real run: data analyst jobs in New York
  5. What it costs
  6. What to watch out for when scraping Indeed
  7. Is Indeed web scraping legal?
  8. Why Scrape.ly for Indeed
  9. FAQ
  10. Does Indeed have an API for job listings?
  11. Can I scrape Indeed with Python?
  12. How do I scrape all the jobs from one company on Indeed?
  13. How many jobs can I scrape from one Indeed search?
  14. How much does it cost to scrape Indeed?

Indeed web scraping turns Indeed's public job search into rows you can sort and filter: one row per job with the title, employer, location, salary, job type, workplace setting, company rating, full description and the employer's own apply link. The quickest way to do it is the Indeed Scraper on Scrape.ly: type a job title and a city, press Start, and export the results for $3.50 per 1,000 listings, with no Indeed account, API key or proxy of your own.

This guide covers the no-code steps, the API (curl, Python, Node.js and AI agents over MCP), what a run costs and what to watch out for. Everything comes from a real run on 23 October 2026: data analyst jobs in New York, with the exact input, output, log and charge.

What data you can get from Indeed

The Indeed Scraper saves one row per job. These are the fields you'll use most:

Field What it holds
positionName, id, url Job title, Indeed job key and the listing link
company, companyIndeedUrl Employer name and its Indeed company page
location Location as Indeed shows it, e.g. "Manhattan, NY" or a city with its ZIP code
salary Pay exactly as Indeed prints it: "$69,400 - $107,600 a year"
jobType, jobSetting, experienceLevel Full-time or part-time, hybrid or remote, entry/mid/senior level
rating, reviewsCount The employer's Indeed rating and review count
externalApplyLink The employer's own application page (Workday, SmartRecruiters, a careers site…)
postingDateParsed, scrapedAt When the job was posted and when you collected it
description, descriptionHTML The full job text, plain and HTML
companyInfo Website, description, logo and employee range, when you switch on company details
searchInput, urlInput Which search or URL produced the row

A field Indeed doesn't show is null (or 0 for rating and review count), so you can filter on it safely.

How to scrape Indeed jobs without code

  1. Open the Actor's page in the Scrape.ly Store.
  2. Pick a ready-made input. We used Data analyst jobs in New York, 10 jobs. "Try this input" opens it in the console.
  3. Change the position, city and country to yours. This is the exact input of our run:
json
{
  "position": "data analyst",
  "location": "New York",
  "country": "US",
  "maxItemsPerSearch": 10,
  "parseCompanyDetails": true
}
  1. Click Start. Ten jobs take a few seconds.
  2. Export the jobs as JSON, CSV or Excel.

The fields that matter most:

  • position and location: the same words you'd type into Indeed's two search boxes. Leave location empty to search the whole country.
  • country: which Indeed site to search (US, GB, DE, IN and 60 more).
  • startUrls: Indeed search URLs with filters already set, single job URLs, or a company's jobs page such as https://www.indeed.com/cmp/Google/jobs. The preset Open jobs at Google, 10 jobs shows that shape. Each URL runs as its own search next to the position search.
  • maxItemsPerSearch: the cap for the position search and for each URL separately (default 10).
  • parseCompanyDetails: adds the employer's website, description, logo and size. It reads the same response as the job, so it adds no extra request.
  • saveOnlyUniqueItems: on by default, so a job found by two of your searches is saved once.

How to scrape Indeed with the API

Create an API token under Settings → API & Integrations and export it as SCRAPELY_TOKEN.

Our run took 6 seconds, so the synchronous endpoint is the simplest option: one request starts the run, waits for it and returns the jobs. Add ?format=csv to the URL for a CSV instead of JSON.

shell
curl -X POST "https://api.scrape.ly/v2/actors/openrole~indeed-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"position": "data analyst", "location": "New York", "country": "US", "maxItemsPerSearch": 10, "parseCompanyDetails": true}'

The synchronous call waits up to 45 seconds. For bigger jobs (hundreds of listings, many start URLs), start the run with /runs instead and read the dataset when it finishes, as the Actors API docs show.

Python (pip install scrapely-client-python). call() waits however long the run takes; this example prints the salary next to the employer and its size:

python
import os
from scrapely_client import ScrapelyClient

client = ScrapelyClient(os.environ["SCRAPELY_TOKEN"], api_url="https://api.scrape.ly")

run = client.actor("openrole/indeed-scraper").call(
    run_input={"position": "data analyst", "location": "New York", "country": "US",
               "maxItemsPerSearch": 10, "parseCompanyDetails": True}
)
print(run.status)  # SUCCEEDED, FAILED, ABORTED or TIMED-OUT
for job in client.dataset(run.default_dataset_id).iterate_items():
    size = ((job.get("companyInfo") or {}).get("companySize") or {}).get("min")
    print(f'{job["salary"] or "no salary shown":34} {job["company"][:32]:32} {size or "?":>6}+ staff  {job["positionName"][:40]}')

Node.js (npm install scrapely-client):

javascript
import { ScrapelyClient } from 'scrapely-client';

const client = new ScrapelyClient({ token: process.env.SCRAPELY_TOKEN, baseUrl: 'https://api.scrape.ly' });

const run = await client.actor('openrole/indeed-scraper').call({
  position: 'data analyst',
  location: 'New York',
  country: 'US',
  maxItemsPerSearch: 10,
  parseCompanyDetails: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((j) => [j.positionName, j.company, j.salary, j.externalApplyLink]));

For AI agents, add the Scrape.ly MCP server. In Cursor, put this in ~/.cursor/mcp.json:

json
{
  "mcpServers": {
    "scrapely": {
      "url": "https://api.scrape.ly/mcp",
      "headers": { "Authorization": "Bearer YOUR_TOKEN" }
    }
  }
}

In Claude Code, run claude mcp add --transport http scrapely https://api.scrape.ly/mcp and sign in with /mcp. Then ask: "Use scrapely to run openrole/indeed-scraper for data analyst jobs in New York, 10 jobs, and give me a table of title, company, salary and apply link." A 10-job run finishes inside the 45 seconds call-actor waits; for larger runs the agent polls get-actor-run and then reads the rows. The Indeed API page shows the same Actor as an API product.

A real run: data analyst jobs in New York

We ran the "Data analyst jobs in New York, 10 jobs" preset on 23 October 2026. It returned 10 jobs in 6 seconds and was charged $0.0360. The first five rows:

Position Company Salary Type Setting Level Rating
Data Analyst I Memorial Sloan Kettering Cancer Center $69,400 - $107,600 a year Part-time Hybrid work Entry level 4.1
Data Analyst NYU Langone Health $70,481.60 - $83,475.00 a year not stated Hybrid work Mid-level 4.3
Regulatory Data Analyst Federal Reserve Bank of New York $79,300 - $85,000 a year Full-time not stated Mid-level 4.1
Business Intelligence Analyst, Product Data Warehouse Google $116,000 - $166,000 a year Full-time not stated Mid-level 4.3
Campus Undergraduate Full-Time Analyst - 2027 Data & Analytics… American Express $78,000 - $124,750 a year Full-time Hybrid work Entry level 4.1

All ten jobs were in New York City, so we left the location column out. Every row is an employer, which is business data, so nothing needed masking. What the full ten show:

  • Pay on every row. All 10 jobs carried an annual salary range, from a $62,000 floor to a $166,000 ceiling. That's a salary benchmark in one six-second run.
  • Direct apply links. All 10 had externalApplyLink pointing at the employer's own system (Workday, SmartRecruiters, a city jobs portal, a careers site), so you can skip Indeed's redirect.
  • Hybrid is the norm here. Six of ten were marked "Hybrid work". Eight were full-time, one part-time and one didn't say.
  • Fresh and stale side by side. Posting dates ran from 10 June to 22 October 2026, with three posted in the last three days. Sort by postingDateParsed before you act on a list.
  • Company context included. With company details on, 9 of 10 rows had the employer's website and 6 were listed at 10,000+ employees. Descriptions ran from about 3,300 to 7,600 characters, ready for keyword or skills analysis.

The log, without the platform's start-up lines:

Text
2026-10-23T03:01:19.484Z [Scrapely] Container started (setup 1.9 s)
2026-10-23T03:01:20.909Z [scrapely.actor] INFO  Searches 1, limit 10 per search.
2026-10-23T03:01:21.002Z [scrapely.proxy_configuration] INFO  Platform proxy access check succeeded.
2026-10-23T03:01:23.430Z [scrapely.actor] INFO  [Status message]: Saved 10 jobs from 1 searches.
2026-10-23T03:01:23.935Z [Scrapely] Container state: status=exited exitCode=0 oomKilled=false

The search itself took about two and a half seconds on the default datacenter proxy. You don't configure anything for it.

What it costs

The Actor charges one event: $0.0035 per job listing saved, which is $3.50 per 1,000. Platform usage (compute, storage and proxy) is billed on top and is small for this Actor.

For our run: 10 jobs × $0.0035 = $0.035 in listing fees. The run was charged $0.0360 in total, because it also used $0.0008 of compute, $0.0001 of storage writes and $0.00005 of datacenter proxy traffic. A 1,000-job pull costs $3.50 in listing fees plus a few cents of usage, and the Free plan's $5 of monthly credit covers it. Set a maximum charge per run on any plan and the run stops before it goes over.

What to watch out for when scraping Indeed

  • One search tops out at about 600 to 750 jobs. Indeed stops paging there even when it reports more. Split big pulls by city, filter or company URL; duplicates across your searches are removed for you.
  • Salary is text. salary is what Indeed prints, including its own estimates when the employer gave none. Parse the numbers and keep the "a year" or "an hour" unit before you compare.
  • Sort on postingDateParsed. It holds each job's posting time as an ISO timestamp, ready to sort and filter. To keep only recent jobs, set the date filter on Indeed's search page and paste that URL into startUrls: filters in a URL are kept.
  • Big runs take minutes. Each page of results is a separate request, so 1,000 jobs take about two to two and a half minutes. Use the asynchronous API or a schedule rather than the synchronous call.
  • Blocks and site changes. The datacenter proxy is the default and worked in our run. If Indeed starts refusing a search, switch proxyConfiguration to residential. Indeed changes its site over time, and the publisher maintains the Actor, so you read fields like salary instead of fixing CSS selectors.
  • People inside descriptions. Some job texts name a recruiter or give a contact address. Employers are businesses; individual recruiters are people. Keep only the fields you need.

The Actor reads public job listings that any visitor can open, without logging in, and it never touches applications, resumes or private messages. Job titles, employers and posted pay are factual business information.

Indeed sets its own rules for automated access in its Terms of Service and its robots.txt file, so read both and check how they apply to your project before you start.

Responsible practice keeps the footprint small: collect public listings only, set maxItemsPerSearch and a maximum charge per run so each job stays at a modest volume, and use the data for analysis rather than republishing listings. Privacy law applies to any person named in a job ad: under the GDPR and the CCPA a recruiter's name or email is personal data, so keep only the fields you need and store them securely. Our guide on whether web scraping is legal covers the general rules. This is not legal advice; before Indeed data goes into a commercial product, get legal advice for your use case.

Why Scrape.ly for Indeed

  • Indeed's APIs are for posting, not pulling. Indeed's developer docs offer the Job Sync API for ATS partners and employers to create and expire jobs, and a Publisher JavaScript Plugin that shows Indeed jobs on a partner's site "with front-end integration only". Credentials come through a Partner Console that Indeed provisions. None of that puts search results in your spreadsheet; the Actor does, in seconds.
  • Rows, not selectors. A hand-written scraper that parses Indeed's HTML needs its own proxies and a fix every time a class name changes. Here you get salary, jobType and externalApplyLink as fields, plus company size and website from the same response.
  • Clear, small cost. $3.50 per 1,000 jobs, a run charge you can cap, and $5 of free credit every month.
  • Every job board in one place. The same publisher, openrole, maintains the Google Jobs Scraper, LinkedIn Jobs, Glassdoor, ZipRecruiter and dozens of national job boards, all with the same API. Our job scraping guide shows how to combine them, and How to scrape LinkedIn jobs covers the LinkedIn side.

FAQ

Does Indeed have an API for job listings?

Not for pulling search results. Indeed's current APIs are partner tools for posting jobs, syncing applications and showing Indeed jobs on a publisher's site through a JavaScript plugin. To get listings as data, use a scraper that reads the public search pages, such as the Indeed Scraper.

Can I scrape Indeed with Python?

Yes. Install scrapely-client-python, call openrole/indeed-scraper with a position and location, and iterate over the dataset, as in the example above. You don't need requests, BeautifulSoup or a proxy pool.

How do I scrape all the jobs from one company on Indeed?

Paste the company's jobs page, for example https://www.indeed.com/cmp/Google/jobs, into startUrls and raise maxItemsPerSearch. Schedule the run daily to watch a competitor's hiring.

About 600 to 750, because Indeed stops paging there. Run several searches in one go (different cities, filters or company pages) to collect more; duplicates are skipped.

How much does it cost to scrape Indeed?

$0.0035 per job, so $3.50 per 1,000, plus a little platform usage. Our 10-job run was charged $0.0360 in total.

  • indeed
  • jobs
  • recruiting
  • salary data
  • python
  • api

About the author

Actor publisher on the Scrape.ly Store

Every open role, wherever it is posted: job boards, company ATS pages and freelance markets in dozens of countries. For recruiters, labour-market analysts and job aggregators. A Scrapely studio.

//Keep reading\\

The internet is full of data.
Start owning yours.

Scrape.ly gives you the infrastructure, the tooling, and the team to extract, process, and operationalize web data at any scale. Setup takes minutes. The advantage lasts forever.

No credit card. No setup calls.