Indeed Jobs Scraper avatar

Indeed Jobs Scraper

Pricing

from $5.00 / 1,000 results

Go to Apify Store
Indeed Jobs Scraper

Indeed Jobs Scraper

Extract detailed job listings from Indeed by search query + filters or by URL. Returns ~60 structured fields per job plus cross-run deduplication, skill/seniority extraction, and normalized salaries.

Pricing

from $5.00 / 1,000 results

Rating

0.0

(0)

Developer

Mukesh Kumar

Mukesh Kumar

Maintained by Community

Actor stats

0

Bookmarked

4

Total users

2

Monthly active users

10 hours ago

Last modified

Categories

Share

Indeed Jobs Scraper — Extract Job Listings, Salaries & Skills from Indeed.com

Indeed Jobs Scraper is a fast, structured Indeed scraper that extracts job postings from Indeed.com and 50+ Indeed country domains (UK, Canada, Australia, India, Germany, France, and more). Search by keyword + filters or paste any Indeed URL and get ~60 structured fields per job as JSON, CSV, Excel, or XML — complete with salary normalization, skills extraction, seniority detection, and cross-run deduplication for scheduled monitoring.

Use it to build job boards, recruiter dashboards, salary benchmarks, hiring intelligence, and ATS pipelines — without writing a single line of scraping code.

Not affiliated with, endorsed by, or sponsored by Indeed. Scrape responsibly and in compliance with applicable laws and Indeed's terms of service.


Why this Indeed scraper

  • Two input modes — supply a list of Indeed URLs or a search query + country + filters. Mix-and-match for any use case.
  • 50+ country domainsindeed.com, uk.indeed.com, ca.indeed.com, de.indeed.com, au.indeed.com, in.indeed.com, and more.
  • Full job details — title, company, location, salary, benefits, full description (text + optional HTML), apply URL, posting date, urgent-hire signals.
  • Skills & seniority extraction — automatic parsing of skills[], detectedSeniority (entry / mid / senior), and minYearsExperience.
  • Normalized salaries — annualized min/max converted to one target currency (USD, EUR, GBP, INR, CAD, AUD, and more) with optional FX overrides.
  • Cross-run deduplication — emit only new jobs since the last run, perfect for scheduled job monitoring (hourly / daily / weekly).
  • Webhook delivery — push new postings to Slack, Make, Zapier, n8n, or your own endpoint on every run finish.
  • Cloudflare-aware — fingerprinted browser over Apify residential proxies with session rotation and challenge detection.
  • Exports everywhere — JSON, CSV, Excel, XML, HTML, or live via the Apify API.

Use cases

Use caseWhat you get
Job board / aggregatorA live feed of Indeed listings with full details, refreshed on a schedule
Recruitment intelligenceTrack competitor hiring — which companies post which roles, where, at what salary
Salary benchmarkingNormalized annual min/max across roles, locations, and seniority levels
ATS / sourcing pipelinesPush new postings into your ATS, CRM, or Airtable via webhook + API
Market & labor analyticsBuild dashboards on hiring volume, remote-work trends, in-demand skills
Lead generationFind companies that are actively hiring for specific tech stacks or roles
Personal job alertsCross-run dedupe + webhook = a private, customized job alert in Slack/email

Quick start

Option A — Scrape by Indeed URL

Paste any Indeed search, company, or filtered URL:

{
"urls": [
"https://www.indeed.com/jobs?q=software+engineer&l=Remote",
"https://www.indeed.com/cmp/Google/jobs",
"https://uk.indeed.com/jobs?q=data+scientist&l=London"
],
"maxRowsPerUrl": 50,
"scrapeJobDetails": true
}

Option B — Scrape by search query + filters

{
"query": "data analyst",
"country": "us",
"location": "New York, NY",
"radius": "25",
"jobType": "fulltime",
"level": "mid_level",
"sort": "date",
"fromDays": "7",
"remote": "remote",
"maxRows": 100,
"scrapeJobDetails": true,
"extractSkills": true,
"normalizeSalary": true,
"targetCurrency": "USD"
}

Input schema

Provide either urls (Option A) or query + country (Option B). All other fields are optional.

Option A — by URL

FieldTypeDefaultDescription
urlsstring[][]Indeed search, company, or filtered URLs. When set, query-based fields are ignored.
maxRowsPerUrlinteger100Maximum jobs to scrape per URL. 0 = no limit.

Option B — by search parameters

FieldTypeDefaultDescription
querystringJob keywords, e.g. "data analyst". Required when not using urls.
countryenumusIndeed country domain. 50+ options including us, gb, ca, au, in, de, fr, es, it, nl, br, mx, jp, sg, ae, and more.
locationstringCity, state, or postcode, e.g. "San Francisco, CA".
radiusenumMiles from location: 0, 5, 10, 15, 25, 35, 50, 100.
jobTypeenumfulltime, parttime, contract, internship, temporary, permanent, seasonal, freelance.
levelenumentry_level, mid_level, senior_level.
sortenumrelevancerelevance or date (newest first).
fromDaysenumPosted within 1, 3, 7, or 14 days.
remoteenumremote or hybrid.
maxRowsinteger100Total cap across all pages. 0 = no limit.
includeSimilarJobsbooleantrueInclude Indeed's "similar to your search" suggestions.

Enrichment & advanced

FieldTypeDefaultDescription
scrapeJobDetailsbooleantrueVisit each job page for the full description, benefits, apply URL, and company profile. Disable for a faster listing-only run.
includeDescriptionHtmlbooleanfalseInclude the raw HTML job description in addition to plain text.
extractSkillsbooleantrueParse the description into skills[], detectedSeniority, and minYearsExperience.
normalizeSalarybooleantrueAdd normalizedSalary{} — annualized min/max in a single target currency.
targetCurrencyenumUSDOutput currency: USD, EUR, GBP, CAD, AUD, INR, SGD, ZAR, BRL, MXN, AED, JPY, CHF, SEK, PLN.
fxRatesOverrideobject{}Override built-in FX rates (units per 1 USD), e.g. {"EUR": 0.92, "GBP": 0.79}.
deduplicatebooleantrueDrop duplicate jobKeys within this run.
onlyNewSinceLastRunbooleanfalseCross-run dedupe — persist seen jobKeys and emit only postings unseen in prior runs.
dedupeStoreNamestringindeed-seen-jobsNamed key-value store used for cross-run deduplication.
notifyWebhookUrlstringOptional. POST a JSON summary of the run's jobs to this URL on finish. Secret.
maxConcurrencyinteger5Max parallel browser pages (1–25). Lower = gentler on anti-bot systems.
proxyConfigurationobjectRESIDENTIALProxy settings. Residential proxies strongly recommended — Indeed blocks datacenter IPs.

Output schema

One record per job is pushed to the default dataset. Below is the full structure with all fields and types.

Example output record

{
"jobKey": "a1b2c3d4e5f67890",
"title": "Senior Data Analyst",
"companyName": "Acme Corp",
"companyUrl": "https://www.indeed.com/cmp/Acme-Corp",
"companyLogoUrl": "https://d2q79iu7y748jz.cloudfront.net/s/_logo/acme.png",
"companyRating": 4.1,
"companyReviewCount": 320,
"city": "New York",
"state": "NY",
"postalCode": "10001",
"formattedLocation": "New York, NY 10001",
"isRemote": false,
"jobType": "Full-time",
"salary": {
"salaryText": "$90,000 - $120,000 a year",
"salaryMin": 90000,
"salaryMax": 120000,
"currency": "USD",
"period": "year"
},
"benefits": ["Health insurance", "401(k)", "Paid time off"],
"descriptionText": "We are looking for a Senior Data Analyst with 5+ years of SQL and Python experience...",
"descriptionHtml": "<p>We are looking for...</p>",
"datePublished": "2026-05-12T00:00:00.000Z",
"age": "3 days ago",
"sponsored": false,
"hiringDemand": {
"isUrgentHire": true,
"isHighVolumeHiring": false
},
"jobUrl": "https://www.indeed.com/viewjob?jk=a1b2c3d4e5f67890",
"applyUrl": "https://www.indeed.com/applystart?jk=a1b2c3d4e5f67890",
"expired": false,
"skills": ["SQL", "Python", "Tableau", "Data Analysis", "Statistics"],
"detectedSeniority": "senior",
"minYearsExperience": 5,
"normalizedSalary": {
"annualMin": 90000,
"annualMax": 120000,
"currency": "USD",
"sourceCurrency": "USD",
"sourcePeriod": "year",
"fxRateApplied": 1,
"isEstimatedFromText": false
},
"source": "indeed",
"scrapedAt": "2026-05-18T10:00:00.000Z"
}

Field reference

Core fields

FieldTypeDescription
jobKeystringIndeed's unique job identifier (used in viewjob?jk=…).
titlestringJob title.
companyNamestringHiring company.
companyUrlstringIndeed company profile URL.
companyLogoUrlstring | nullCompany logo image URL.
companyRatingnumber | nullCompany rating on Indeed (0–5).
companyReviewCountnumber | nullNumber of Indeed reviews.
citystring | nullCity.
statestring | nullState / region code.
postalCodestring | nullPostal / ZIP code.
formattedLocationstringHuman-readable location string.
isRemotebooleanWhether the role is remote / hybrid.
jobTypestring | nullEmployment type — Full-time, Contract, etc.

Salary, benefits, description

FieldTypeDescription
salary.salaryTextstring | nullRaw salary text from Indeed.
salary.salaryMinnumber | nullParsed minimum (source currency, source period).
salary.salaryMaxnumber | nullParsed maximum (source currency, source period).
salary.currencystring | nullSource currency code (e.g. USD, GBP, EUR).
salary.periodstring | nullPay period — hour, day, week, month, year.
benefitsstring[]Listed benefits, e.g. ["Health insurance", "401(k)"].
descriptionTextstringPlain-text job description.
descriptionHtmlstring | nullRaw HTML description (only when includeDescriptionHtml: true).
FieldTypeDescription
datePublishedstring (ISO 8601)When the job was first posted on Indeed.
agestringHuman-readable age, e.g. "3 days ago".
sponsoredbooleanWhether this is a sponsored / promoted listing.
hiringDemand.isUrgentHirebooleanIndeed's "Urgently hiring" badge.
hiringDemand.isHighVolumeHiringbooleanHigh-volume hiring signal.
jobUrlstringCanonical Indeed job URL.
applyUrlstring | nullDirect apply URL (Indeed Apply or external ATS).
expiredbooleanWhether the posting is expired / no longer accepting applications.
sourcestringAlways "indeed".
scrapedAtstring (ISO 8601)Timestamp of when this record was collected.

Enrichment fields (when enabled)

FieldTypeDescription
skillsstring[]Detected skills from title + description (e.g. SQL, Python, AWS, React).
detectedSenioritystring | nullentry, mid, or senior.
minYearsExperiencenumber | nullMinimum years of experience parsed from the description.
normalizedSalary.annualMinnumber | nullAnnualized minimum in targetCurrency.
normalizedSalary.annualMaxnumber | nullAnnualized maximum in targetCurrency.
normalizedSalary.currencystringOutput currency (defaults to USD).
normalizedSalary.sourceCurrencystringOriginal currency on the posting.
normalizedSalary.sourcePeriodstringOriginal period (hour, year, etc.).
normalizedSalary.fxRateAppliednumberFX rate used for conversion.
normalizedSalary.isEstimatedFromTextbooleantrue if the salary was parsed from free-text vs structured data.

Scheduled monitoring — get only new Indeed jobs

Set up an automated Indeed job monitor that delivers only fresh postings:

  1. Enable "onlyNewSinceLastRun": true and pick a dedupeStoreName.
  2. Set "notifyWebhookUrl" to your Slack incoming webhook, Make/Zapier scenario, n8n workflow, or any HTTPS endpoint.
  3. Schedule the actor (hourly / daily) from the Apify Console → Schedules.
  4. Each run's dataset (and webhook payload) contains only postings unseen in prior runs.

Webhook payload (POST, application/json)

{
"event": "indeed.jobs.scraped",
"finishedAt": "2026-05-18T10:00:00.000Z",
"runId": "abc123",
"datasetId": "xyz789",
"datasetItemsUrl": "https://api.apify.com/v2/datasets/xyz789/items?format=json",
"source": {
"query": "data analyst",
"country": "us",
"location": "New York, NY"
},
"monitoring": true,
"totalJobs": 18,
"delivered": 18,
"truncated": false,
"jobs": [
{
"title": "Senior Data Analyst",
"companyName": "Acme Corp",
"salaryText": "$90,000 - $120,000 a year",
"normalizedSalary": {
"annualMin": 87692,
"annualMax": 131538,
"currency": "USD"
},
"skills": ["SQL", "Python"],
"jobUrl": "https://www.indeed.com/viewjob?jk=...",
"applyUrl": "https://..."
}
]
}

Up to 200 jobs are inlined; if truncated is true, fetch the rest from datasetItemsUrl. Test with a free webhook.site URL before connecting production endpoints.


Output formats & integrations

Pull results as:

  • JSONhttps://api.apify.com/v2/datasets/{DATASET_ID}/items?format=json
  • CSV?format=csv
  • Excel?format=xlsx
  • XML / HTML / JSONL — same dataset endpoint, different format param

Native integrations: Make, Zapier, n8n, Slack, Airtable, Google Sheets, Pipedream, plus generic webhooks.


Pricing

This actor is priced per scraped job result via Apify's built-in pay-per-result billing.

  • Measured Apify usage — approximately $6–7 per 1,000 jobs of platform cost (browser compute + residential proxy bandwidth).
  • Recommended retail price$8–12 per 1,000 jobs to maintain margin, or compete on enrichment + monitoring differentiators rather than raw price.
  • Re-measure with apify run after any change before locking the production price.

The pay-per-event configuration lives in .actor/pay_per_event.json. Final pricing is set in Apify Console → Publication → Monetization.


Realistic expectations & limits

Indeed is Cloudflare-protected and aggressively rate-limits deep pagination. This actor uses a fingerprinted headless browser over Apify residential proxies — the most reliable self-contained approach. Plan accordingly:

  • Dependable yield — ~1 results page per search (~15–25 jobs) plus full job details. Deeper pagination is attempted, but treat page 1 as the reliable floor.
  • Best fits — scheduled monitoring (only-new-since-last-run), niche / targeted queries, salary and skill intelligence.
  • Less suited for — bulk multi-thousand-job harvests in a single run. For that, run multiple narrower queries on a schedule.

How it works

A single PlaywrightCrawler with:

  • Browser fingerprint injection to look like real users.
  • Apify residential proxies with session rotation per blocked request.
  • Cloudflare challenge detection — interstitials (title, page markers, status codes) are detected and the session is retired and retried on a fresh fingerprint.
  • Bandwidth control — images, fonts, media, and trackers are blocked at the network layer.

Parsing strategy

  • Listing pages — extracts window.mosaic.providerData["mosaic-provider-jobcards"] JSON via a brace-matching extractor, with explicit "no results" vs "soft block" detection.
  • Detail pages — deep-searches window._initialData for the job model (resilient to Indeed reshaping JSON paths), with <title> + regex fallbacks.
  • Paginationstart parameter is stepped by Indeed's real page size (15). Stops at the last (short) page instead of hammering a dead offset and triggering blocks.

If Indeed changes its private remote/hybrid filter codes, the actor falls back to a query keyword so runs don't fail outright.


Local development

git clone <this-repo>
cd indeed-jobs-scraper
npm install
# Edit your test input:
# storage/key_value_stores/default/INPUT.json
npx apify run # or: npm start

Requirements: Node.js 20+, Docker (for Apify build), and an Apify account if you want to deploy.


FAQ

The actor scrapes only publicly accessible Indeed job postings. You are responsible for using the data in compliance with applicable laws (GDPR, CCPA, etc.) and Indeed's terms of service. This actor is not affiliated with Indeed.

Why are residential proxies required?

Indeed is protected by Cloudflare and blocks datacenter IPs almost instantly. The default Apify residential proxy group is strongly recommended — without it, runs will fail with Cloudflare challenges.

Can I scrape Indeed UK, Canada, Germany, or India?

Yes. Set the country field to any of 50+ supported domains (gb, ca, de, in, fr, au, etc.), or paste country-specific Indeed URLs directly into urls.

How do I get only new jobs since my last run?

Enable "onlyNewSinceLastRun": true and set a dedupeStoreName. Pair with a schedule and a notifyWebhookUrl for a fully automated Indeed job alert in Slack, Make, Zapier, or your own system.

Does it return salary data?

Yes — both raw salary text (salary.salaryText) and normalized annualized salaries (normalizedSalary.annualMin/annualMax) converted to your chosen targetCurrency. Hourly, weekly, and monthly rates are auto-annualized.

Can it extract skills from job descriptions?

Yes — enable extractSkills (default true). The actor parses the title + description into a skills[] array, plus detected seniority and minYearsExperience.

How fast is the actor?

With defaults (maxConcurrency: 5, full job details enabled), expect ~3–6 seconds per job end-to-end (browser load + Cloudflare wait + detail parse). Disable scrapeJobDetails for a 5× faster listing-only run.

What if Indeed changes its HTML?

Parsing reads embedded JSON (window.mosaic.providerData, window._initialData) rather than DOM selectors, which is far more resilient to HTML reshuffles. Where regex fallbacks exist, they are documented in src/parsers/.

Can I get the full HTML description?

Yes — set "includeDescriptionHtml": true to receive descriptionHtml alongside descriptionText.


  • LinkedIn Jobs Scraper — extract jobs from LinkedIn
  • Glassdoor Jobs Scraper — jobs + company reviews + salary data
  • ATS Jobs Scraper — direct from Greenhouse, Lever, Ashby, Workday career pages
  • Job Posting Aggregator — combine Indeed + LinkedIn + Glassdoor into one feed

Support

Open an issue or contact the maintainer via the Apify Store actor page. Bugs reproduced with a public input JSON + a non-blocked URL are the fastest to resolve.

Tags: indeed scraper, indeed jobs scraper, scrape indeed, indeed api, job scraper, jobs api, salary data, recruitment data, hiring intelligence, job board scraper, indeed.com scraper, job listings scraper, ATS feed, job aggregator, indeed UK, indeed Canada, indeed India.