Indeed has no job search API, and a plain request gets a security check, not jobs. Web Unlocker opens the search in a real browser and returns the page with 50 job cards, salary and rating included, billed as one page: 1,000 jobs for 2 cents.
1,000 free requests · Pay only on success · 195+ countries · Claude ready
Indeed pages take 10 to 30 seconds. It is still working, no need to click again.
Ready for your own searches? Get an API key or book a demo.
Tell us a little about your team and get:
An Indeed search page carries 50 job cards with salary, company rating and posted date, and Web Unlocker bills it as one page. Indeed scrapers on Apify and Bright Data bill every job as a record of its own.
One page, 50 jobs.
Twenty pages, 1,000 jobs.
Need the full description? Each job's own page is one more request, so 1,000 jobs with descriptions come to $1.00. Slice a market by date, radius, salary or remote work, and every slice is one more page of 50.
What 1,000 Indeed jobs with full descriptions cost
20 search pages and 1,000 job pages, at published prices
Bright Data, Apify's own Indeed Scraper and the Indeed Job Scraper on Apify bill each job as a record: 1,000 records at pay-as-you-go and Free plan prices.
One hundred Indeed pages asked for one after another, no retries: job searches in 40 cities, job pages, company pages, review pages and four country sites. This is what comes back, and how fast.
A page that does not come back costs $0.
You pay for pages, not for tries.
Sort a search by date or keep it to the last 24 hours, and the newest postings sit on top. Your script asks for that one page as often as you like: a check every hour is 24 pages a day, $0.71 a month.
Job datasets refresh monthly.
Your page is today's.
Indeed scraping without HTML parsing: Indeed's own JSON sits inside every search page, 50 jobs with title, company, salary, rating and posted date. A job's page carries its full description the same way.
Fill in the form above. The key arrives by email within one business day.
A search with limit=50 for 50 jobs, a job's page for its description, or a company's jobs tab, page after page.
Cut the results array out of the page and you have every field Indeed shows, from salary to posted date.
pip install requestsGOLOGIN_SCRAPING_API_KEY=your_key python indeed_jobs.pyhttps://parsing.webunlocker.gologin.com/v1/scrapeimport json
import os
import time
from urllib.parse import urlencode
import requests
query = "marketing manager"
where = "Austin, TX"
def scrape(url):
for attempt in range(3):
response = requests.get(
"https://parsing.webunlocker.gologin.com/v1/scrape",
params={"url": url, "geo": "us", "format": "json"},
headers={"apikey": os.environ["GOLOGIN_SCRAPING_API_KEY"]},
timeout=90,
)
page = response.json().get("data", {}).get("html", "") if response.ok else ""
if '"results":[{' in page:
return page
time.sleep(5)
raise RuntimeError("No jobs came back, try again later")
def embedded(page, marker):
start = page.index(marker) + len(marker) - 1
value, _ = json.JSONDecoder().raw_decode(page[start:])
return value
search = "https://www.indeed.com/jobs?" + urlencode({"q": query, "l": where, "limit": 50})
page = scrape(search)
jobs = [
{
"title": job["title"],
"company": job["company"],
"location": job["formattedLocation"],
"salary": (job.get("salarySnippet") or {}).get("text"),
"rating": job.get("companyRating") or None,
"posted": job.get("formattedRelativeTime"),
"sponsored": job.get("sponsored", False),
"url": "https://www.indeed.com/viewjob?jk=" + job["jobkey"],
}
for job in embedded(page, '"results":[')
]
print(json.dumps({"query": query, "where": where, "jobs_on_page": len(jobs), "jobs": jobs}, indent=2, ensure_ascii=False))GOLOGIN_SCRAPING_API_KEY=your_key node indeed-descriptions.mjshttps://parsing.webunlocker.gologin.com/v1/scrapeconst search = 'https://www.indeed.com/jobs?q=registered+nurse&l=Remote&limit=50';
const wanted = 5;
async function scrape(url, marker) {
for (let attempt = 1; attempt <= 3; attempt++) {
const params = new URLSearchParams({ url, geo: 'us', format: 'json' });
const response = await fetch(`https://parsing.webunlocker.gologin.com/v1/scrape?${params}`, {
headers: { apikey: process.env.GOLOGIN_SCRAPING_API_KEY },
});
const page = response.ok ? (await response.json()).data?.html : '';
if (page?.includes(marker)) return page;
await new Promise((resolve) => setTimeout(resolve, 5000));
}
throw new Error(`Nothing came back for ${url}, try again later`);
}
const field = (page, name) => JSON.parse(page.match(new RegExp(`"${name}":("(?:[^"\\\\]|\\\\.)*")`))?.[1] ?? '""');
const text = (markup) => markup.replace(/<[^>]+>/g, ' ').replace(/&/g, '&').replace(/ /g, ' ').replace(/\s+/g, ' ').trim();
const listing = await scrape(search, '"results":[{');
const keys = [...new Set([...listing.matchAll(/"jobkey":"([0-9a-f]{16})"/g)].map((match) => match[1]))];
console.log(`${keys.length} jobs on the search page, reading the first ${wanted} in full`);
for (const key of keys.slice(0, wanted)) {
const job = await scrape(`https://www.indeed.com/m/viewjob?jk=${key}`, '"sanitizedJobDescription"');
const description = text(field(job, 'sanitizedJobDescription'));
console.log(`\n${field(job, 'jobTitle')} at ${field(job, 'companyName')}, ${field(job, 'formattedLocation')} (${description.length} characters)`);
console.log(`${description.slice(0, 280)}...`);
}GOLOGIN_SCRAPING_API_KEY=your_key node indeed-company.mjshttps://parsing.webunlocker.gologin.com/v1/scrapeconst company = 'Amazon.com';
const pages = 5;
async function scrape(url) {
for (let attempt = 1; attempt <= 3; attempt++) {
const params = new URLSearchParams({ url, geo: 'us', format: 'json' });
const response = await fetch(`https://parsing.webunlocker.gologin.com/v1/scrape?${params}`, {
headers: { apikey: process.env.GOLOGIN_SCRAPING_API_KEY },
});
const page = response.ok ? (await response.json()).data?.html : '';
if (page?.includes('"results":[{')) return page;
await new Promise((resolve) => setTimeout(resolve, 5000));
}
throw new Error(`No jobs came back for ${url}, try again later`);
}
function embedded(page, marker) {
const start = page.indexOf(marker) + marker.length - 1;
const token = /"(?:[^"\\]|\\.)*"|[[\]{}]/g;
token.lastIndex = start;
let depth = 0;
for (let match = token.exec(page); match; match = token.exec(page)) {
depth += '[{'.includes(match[0]) ? 1 : ']}'.includes(match[0]) ? -1 : 0;
if (depth === 0) return JSON.parse(page.slice(start, match.index + 1));
}
return [];
}
const jobs = new Map();
for (let start = 0; start < pages * 20; start += 20) {
const page = await scrape(`https://www.indeed.com/cmp/${company}/jobs?start=${start}`);
const fresh = embedded(page, '"results":[').filter((job) => !jobs.has(job.jobkey));
if (fresh.length === 0) break;
for (const job of fresh) {
jobs.set(job.jobkey, { title: job.title, location: job.formattedLocation, salary: job.salarySnippet?.text ?? null, posted: job.formattedRelativeTime });
}
console.log(`page ${start / 20 + 1}: ${fresh.length} new jobs, ${jobs.size} so far`);
}
console.table([...jobs.values()].slice(0, 10));Ready to run it on your own searches? Get your API key and free requests.
Indeed has no job search API for your app, and every ready Indeed job scraper bills every job. Here is what each route to Indeed job data takes for the same 1,000 jobs.
| What it takes | Indeed API | Apify | Bright Data | |
|---|---|---|---|---|
| Job search data for your app | Yes | No | Yes | Yes |
| You pay for | A page | No API | Each job | Each record |
| 1,000 jobs with descriptions | $1.00 | None | $0.10 to $6 | $1.50 |
| Jobs in one request | 50 | None | Up to 1,000 a run | One record |
| Page 2 and beyond | From your account | None | Yes | Yes |
| Company reviews and salaries | Yes | No | No | Yes |
| The whole page in HTML or Markdown | Yes | No | No | No |
| Free to start for business | Yes | No | Yes | Yes |
Scraping more than Indeed? Web Unlocker returns any page the same way, at the same price.
Web Unlocker returns one page per request, and Indeed shows a visitor one page of jobs before it asks for a sign-in. Add Cloud Browser for the rest: it logs into your Indeed account once and keeps that session alive, so page 2, page 20 and page 50 open the way page 1 does, on the same device and the same residential IP.
Your session is a browser that is still running, not a file you restored. Close your script tonight, open it tomorrow, and Indeed sees the same signed-in visitor.
The profile keeps its cookies and its residential IP from run to run, so Indeed sees one person on one laptop, not a new visitor on every page.
Drive it from Playwright, Puppeteer or Claude: open the search, click next, read the cards, repeat. Residential IPs included.
Run it from Playwright, Puppeteer or Claude. See Cloud Browser.
One account, one proxy network, one ecosystem. Pick the product the job needs.
Scraping other sites too? See the Google Maps, Reddit and YouTube scrapers, or every web scraping tool.
Send the Indeed search address to Web Unlocker with limit=50 in it. It opens the page in a real browser from a US address and returns it with Indeed's own JSON inside: 50 jobs with title, company, location, salary, company rating, posted date and link. The code above does exactly that.
Not for job search. The old Publisher API that returned job listings is closed, and Indeed's current APIs are for partners that send jobs to Indeed, such as applicant tracking systems. None of them returns search results to a third party, so a scraper reads the public pages instead.
There is no Indeed API you can pay for or get for free to read job postings. Web Unlocker starts with 1,000 free requests, enough for 1,000 search pages of 50 jobs each.
Indeed's terms are written for people looking for a job, and its job search pages are public and load without a login. Web Unlocker loads them the way a visitor's browser does and returns the page. What you collect and how you use it is your call, so check it against the rules that apply to your project.
Job postings are published to be read, and scrapers read the public pages a visitor sees. The rules differ by country and by what you do with the data, so check your own use against them. Web Unlocker returns the page; the rest is yours.
A program that loads job board pages and pulls out the postings as data: title, company, location, salary, date, description and link. On Indeed, Web Unlocker does the loading part, and the fields come out of the JSON Indeed embeds in every page.
Send the search address to the Indeed scraper API with the requests library, find the results array in the page and load it with the json module. The Python tab above is a complete script, tested on the live API: 50 jobs in one request.
Each job's page carries it. Open indeed.com/m/viewjob?jk= followed by the job key through Web Unlocker: it returns the description as text and HTML, with the title, company, location and job type. One page per job, 1,000 descriptions for $0.98.
Indeed shows a visitor one page of a search and asks for an account on the next. Two ways around it: slice the search by date, radius, salary or remote work, and each slice is a fresh page of 50 jobs; or open Indeed in Cloud Browser, log into your own account once, and page through from that session.
As fresh as Indeed itself. Sort a search by date or filter it to the last 24 hours and the page shows postings marked Just posted. Job datasets sold by the record refresh weekly or monthly; a page is today's page.
Yes. A company's page returns its rating, review count and open jobs; its reviews pages return 20 reviews each with pros and cons, page after page; its salaries page lists pay by job title. The salary pages by title and city work too, with the average and the range.
Yes. The UK, Canadian, German and French Indeed sites returned their search pages the same way, with the exit country set to match. Send the country site's address and pick the geo in the request.
Web Unlocker returns the page, not a table, so nothing is cut before you see it. Turning the jobs into a CSV takes a few lines of code, or one prompt: hand the page to Claude with the Gologin scraping skill and ask for a CSV.
Under $1 per 1,000 pages on the Indeed scraper API, and the same for every page. A search page holds 50 jobs, so 1,000 jobs from search pages cost 2 cents, and 1,000 jobs with their full descriptions about $1. A page that fails to load costs nothing. Plans start at $49 a month for 50,000 requests.
Yes, to start: Web Unlocker comes with 1,000 free requests, enough for 50,000 jobs from search pages or 1,000 full job descriptions.
It depends on what you need. Apify's Indeed scrapers and Bright Data sell ready rows and bill every job as a record, and their users report runs that come back with fewer jobs than Indeed shows. Web Unlocker is an Indeed web scraper that returns the whole page at one price, with Indeed's own JSON inside, so you decide which fields you keep.
Tell us a little about your team and get: