I needed Indeed jobs in a spreadsheet every morning. Title, company, salary, the full description. Should have been a ten minute job. It wasn't.
The Problem
First stop was the official API. There isn't one anymore, at least not for reading jobs. Indeed's own developer docs mark the old job search endpoints as deprecated and closed to new integrations. If a tutorial calls api.indeed.com/ads/apisearch with a publisher= key, it's dead. What Indeed offers today goes the other way: APIs for employers and ATS vendors to push jobs and applications into Indeed. None of them gives you search results.
So I tried scraping it myself. Three walls, in this order.
- Cloudflare. A plain Node HTTP client got the Cloudflare challenge on 50 out of 50 searches in my test.
-
The login wall on page 2. Logged out, page 1 of a search loads. Page 2 redirects to Indeed's sign in page. I measured this on October 7, 2026, even in a session that had just loaded page 1 fine. So the classic
&start=10loop is gone. - The job page wants you signed in too. Opening a job URL while logged out gets you a sign in prompt instead of the description.
The data is right there on the search page, and you still can't get it out cleanly.
Who runs into this: recruiting agencies building daily feeds, people running niche job boards, salary research, and sales teams who want to know which companies are hiring for roles their product serves.
The Solution
I ended up building it as an Apify actor: Indeed Jobs Scraper. You give it keywords and a location, or paste Indeed search URLs, and it gives back clean rows. No Indeed account, no cookies.
Each row has:
- title, company, company rating and review count
- location split into city, state and country
- salary text plus parsed
salaryMin,salaryMax, currency and period (yearly or hourly), only when Indeed publishes them. Nothing is estimated. - job types and benefits
- posted date as a timestamp, plus the "3 days ago" text Indeed shows
- the full description as plain text and HTML, on by default
How does it get past page 1 without logging in? It doesn't fight the wall. It runs narrower versions of your search instead: newest first, shorter posting windows, smaller radius, job type filters. Each version has its own page 1, and the results get deduplicated. In one test, a Chicago registered nurse search gave 47 jobs on its first page and 225 unique jobs across nine of these versions. That test used a wider radius than the actor uses, so your numbers will differ.
What I measured on October 7, 2026, on the Apify platform:
- a Chicago nurse search with
maxJobs: 100returned 100 jobs, all 100 with full descriptions - a London run returned 15 of 15 descriptions
- in five runs, the charged events matched the delivered rows exactly
- a repeat run with
onlyNewskipped the 28 jobs it had already sent and delivered only new ones
That last one is the monitor mode. Set onlyNew: true, schedule it, and each run only returns jobs you haven't received in the last 90 days.
Quick Start (3 Minutes)
- Open Indeed Jobs Scraper on Apify and click Try for free.
- Paste this into the JSON input:
{
"keywords": "registered nurse",
"location": "Chicago, IL",
"country": "us",
"maxJobs": 20
}
- Click Start. When it finishes, export the dataset as CSV, JSON or Excel.
It covers the US, UK, Germany, Australia and Canada.
Code Example
Python, with the official client (pip install apify-client):
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("agnes.developer.queen/indeed-jobs-scraper").call(run_input={
"keywords": "data engineer",
"location": "Remote",
"country": "us",
"postedWithinDays": 7,
"titleExcludes": ["intern"],
"maxJobs": 100,
})
jobs = client.dataset(run["defaultDatasetId"]).list_items().items
for job in jobs:
if job["salaryMin"]:
pay = f'{job["salaryMin"]} to {job["salaryMax"]} {job["salaryCurrency"]} {job["salaryPeriod"]}'
else:
pay = "no salary listed"
print(job["title"], "|", job["companyName"], "|", pay)
If you prefer a single HTTP call, the sync endpoint runs the actor and returns the items in one response:
curl -X POST "https://api.apify.com/v2/acts/agnes.developer.queen~indeed-jobs-scraper/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"keywords":"accountant","location":"New York, NY","maxJobs":20}'
In n8n the Apify node does the same thing: Run an Actor, wait for it to finish, then send the items to Google Sheets. I'll write that one up separately.
Use Cases
-
Recruiting feeds. Save a task per client search with
onlyNew: trueand schedule it every morning. Only new jobs land in the sheet. - Job boards. Backfill a niche board with titles, companies, salaries and full descriptions.
- Salary research. Compare published ranges by city. Missing salaries stay empty, they're never guessed.
- Hiring signals for sales. Watch which companies post roles your product serves, for example "data engineer" if you sell data tooling.
Pricing
You pay per delivered job. $0.002 per job with a full description, $0.001 per job without one. So 100 jobs with descriptions cost $0.20, and 1,000 cost $2. Apify adds a tiny start fee per run.
Duplicates, jobs your title filters remove, and jobs skipped by onlyNew are never charged. If a description can't be fetched, that row drops to the cheaper price.
FAQ
Does Indeed still have a public jobs API?
Not for search. The old job search endpoints are marked deprecated in Indeed's developer docs. The current APIs are for employers posting jobs and handling applications.
Do I need an Indeed account or cookies?
No. It only reads public pages, logged out.
Why did my run stop before maxJobs?
Without a login only page 1 of each search version is reachable. When new versions stop adding jobs, the run stops. The run's RUN_SUMMARY record tells you why.
Is scraping Indeed legal?
It reads publicly visible job postings, but Indeed's terms restrict automated access, so check your use case and talk to a lawyer if you're building a commercial product on top of it. It doesn't return personal data about job seekers, and you shouldn't use it to collect any.
Get Started
Run Indeed Jobs Scraper on Apify. Start with maxJobs: 20 and see what your search actually returns. If something breaks, open an issue on the actor page with your run ID and I'll look at it.
Top comments (0)