🏆 AI Powered Indeed Job Scraper ⚡ Advanced Data, Best Value 🏆
Pricing
Pay per usage
🏆 AI Powered Indeed Job Scraper ⚡ Advanced Data, Best Value 🏆
Scrape job postings from Indeed by keyword, location, and country. Structured output with title, company, location, salary, job type, and apply link.
Pricing
Pay per usage
Rating
5.0
(1)
Developer
Muhammad Shamshad Aslam
Maintained by CommunityActor stats
8
Bookmarked
1
Total users
1
Monthly active users
16 hours ago
Last modified
Categories
Share
Indeed Jobs Scraper
Extract job postings from Indeed at scale — by keyword, location and country across 15 country sites. Every record arrives complete and structured, with the full job description included — no browser, no login wall, no page-1 limit.
Then it goes further than a scraper: pull an employer's other open roles, strip out recruitment agencies, and push each job straight into your workflow as it's found.
Features
Core scraping
- ✅ Unlimited pagination — ~100 jobs per request. No "page 2 requires login" wall
- ✅ 15 country sites — US, Germany, UK, Canada, Australia, France, Spain, Italy, Netherlands, Austria, Switzerland, India, Pakistan, UAE, Singapore
- ✅ 64 fields per job — grouped into job details, job location, salary and company details so related columns sit together
- 📍 Complete addresses, kept apart —
jobLocationFullfor where the job is,companyAddressFullfor the employer's head office. Never conflated - 💰 Readable salary —
salaryFormattedgives you$70,000 - $90,000 per yearat a glance, plus annualisedsalaryMinPerYear/salaryMaxPerYearso hourly, monthly and yearly jobs sort against each other - ✅ Deep company data — full "about" text, CEO name and photo, website, domain, industry, size, revenue, logo, and social links
- ✅ Stable attribute keys — every attribute and occupation carries Indeed's own key alongside the label, so you can match across countries where the labels are translated
- ⭐ Company ratings (optional) — star rating, review count, CEO approval and headquarters, off by default
- ✅ Duplicate-free — every job deduplicated by Indeed job key across the search, the per-company cap and the company expansion alike
Beyond a plain scraper
- 🔑 Multi-keyword & multi-location search — Indeed caps every search at 1,000 results. Run several keywords and locations in one go, each with its own 1,000 allowance, aggregated into a single deduplicated dataset
- 🏢 More jobs from the same company — for every employer in your results, pull their other open roles. Matched on Indeed's company ID, so "Delta" never drags in Delta Dental or Delta Defense
- 🤖 Recruitment-agency filter — flag or remove staffing firms and headhunters, so you keep only companies hiring for themselves. Free keyword screen plus optional AI on the company description
- ⚡ Per-job webhook — each job POSTed to your endpoint the moment it's found, one request per job, ready for Clay, n8n, Make or Zapier. No dataset fetch afterwards
- 🧹 Clean output — industry labels normalised from Indeed's internal enums, nested extras kept out of your main results
Why this one
If you're comparing Indeed scrapers, these are the things worth checking:
| This actor | What to watch for elsewhere | |
|---|---|---|
| Pagination | Unlimited | Many stop at page 1 — Indeed's web search demands a login for page 2 |
| Descriptions | Full text on every job | Often needs a second request per job, or is dropped entirely |
| Speed & cost | No headless browser; cheap datacenter proxy | Browser-based scrapers burn compute; residential-proxy ones bill per gigabyte |
| Company matching | Exact Indeed company ID | Name matching silently merges Delta Dental, Delta Defense and Delta College |
| Agency filtering | Built in, free screen + optional AI | Usually absent — you filter recruiters out by hand afterwards |
| Delivery | Per-job webhook into your workflow | Dataset only; you write the fetch-and-poll glue yourself |
Accuracy is measured, not asserted. Company expansion was tested across 56 rows spanning 28 employers, including two separate MasTec entities, with zero wrong-company rows. The agency screen was tested against 26 companies with zero false positives — Staffordshire, Cheshire, Hampshire, Berkshire and Jobst Medical all correctly pass, where a naive keyword filter deletes them.
Nothing is dropped on a guess. Employers with no Indeed description, and any company the AI could not judge, are kept and labelled rather than silently removed.
Input
| Field | Type | Default | Description |
|---|---|---|---|
country | select | us | Indeed country site |
keywords | array | required | One or more job titles, skills or companies — at least one. Each is searched separately and the results merged, deduplicated by job key |
locations | array | [] | One or more cities, states or postal codes. Every keyword is searched in every location. Empty = country-wide |
radius | integer | 25 | Search radius (km/miles by country); used with location |
postedWithinDays | integer | 0 | Only jobs from the last N days (0 = all time) |
maxItems | integer | 50 | Total number of records to scrape |
maxJobsPerKeyword | integer | 0 | Cap how many jobs each keyword contributes, so a broad term can't eat the whole budget. 0 = no limit |
fetchCompanyRatings | boolean | false | Star rating, reviews, CEO approval, HQ. Needs residential proxy |
maxJobsPerCompany | integer | 0 | Keep at most N jobs per employer (2–10, max 10). 0 (default) = no limit |
fetchMoreCompanyJobs | boolean | false | Off by default. Turn on to also collect other open roles from each company found |
moreJobsPerCompany | integer | 5 | How many other jobs to pull per company (up to 25). Only used when the switch is on |
expandedJobsOutput | select | nested | nested = extras hang off each search row; separateRows = extras become their own rows |
recruitmentAgencyHandling | select | off | off / flag (label them) / exclude (drop them). Needs an OpenAI key |
openaiApiKey | string | — | Your OpenAI API key, used only for the filter above |
openaiModel | select | gpt-4o-mini | Fixed — the nano models gave unreliable verdicts and were removed |
webhookUrl | string | — | Optional. POST each job to your URL as it's found, one request per job |
proxyConfig | proxy | off | Optional — the Actor enables a datacenter proxy itself when a run needs one. Only set Residential if you turned on employer ratings |
Searching several keywords and locations
Indeed caps every individual search at 1,000 results — that ceiling is theirs, not this Actor's, and no amount of pagination gets past it. The way through is to run several narrower searches instead of one broad one, because each gets its own 1,000-result allowance.
{"country": "nl","keywords": ["monteur", "hovenier", "uitvoerder"],"locations": ["Amsterdam", "Rotterdam", "Utrecht"],"maxItems": 500,"maxJobsPerKeyword": 200}
That runs 9 searches (3 keywords x 3 locations), each with its own allowance, and returns one clean dataset.
Overlap is handled for you. A job that shows up under both "monteur" and "uitvoerder" is
deduplicated by Indeed's job key, so it appears once — never twice. The keyword and
location fields on each row tell you which search first surfaced it.
The older keyword / extraKeywords / location / extraLocations fields still work, so saved tasks and existing API calls keep running unchanged.
maxJobsPerKeyword keeps one term from starving the others. Without it, a broad keyword
is scraped first and can consume the entire Max Items budget before the rest ever run. With
3 keywords, Max Items 60 and this set to 20, each keyword contributes at most 20.
What runs when
Everything is scraped and aggregated before any filtering or delivery happens. This ordering matters if you use the AI filter or a webhook:
- Scrape every keyword/location combination, deduplicating by job key as it goes
- Aggregate all searches into one result set
- Expand by company, if that option is on
- Apply the per-company cap across the aggregated set
- Classify companies — the unique employers are collected first, and each is judged once, no matter how many keywords or how many jobs it appeared under
- Deliver to the dataset and to your webhook
So a company found under three different keywords costs one AI call, not three. And your webhook never receives a row before its classification is settled, nor twice for the same job.
Max jobs per company
One employer can swamp a search — a single "nurse" search returned 26 of its 60 jobs from
one company. maxJobsPerCompany caps how many rows any one employer contributes, so you
see the market instead of one company's careers page.
| Setting | Result on the same 60-job search |
|---|---|
2 | 41 companies, none above 2 |
3 | 35 companies, none above 3 |
0 (default) = no limit | 18 companies, one contributing 26 jobs |
0 means unlimited, not "one per company". There is no cap at all: whatever a company has
that matches your search, you get, up to maxItems. If an employer has 200 matching jobs and
you ask for 200 rows, all 200 rows can come from that single employer. Use 0 only when you
want everything from a narrow search — for example when you have already searched a specific
company by name.
Defaults to 0 (no limit), so you get everything Indeed returns. Set 2–10 when you would rather have breadth across employers than depth on one; 10 is the maximum.
Skipped jobs do not count towards maxItems — you still get the full number you asked
for, just spread across more employers. Employers are grouped by their Indeed company ID,
falling back to the normalised company name so smaller firms with no company page are capped
too. Every job is still deduplicated by job key, and a job dropped by the cap is remembered so
the company expansion cannot add it back.
Careful —
0means opposite things on the two company settings. OnmaxJobsPerCompanyit means no limit. OnmoreJobsPerCompany(below) it means off.
This setting is the mirror image of the next one: maxJobsPerCompany limits how many of a
company's jobs matched your search, while fetchMoreCompanyJobs deliberately adds more of
them. They combine well — cap the search for breadth, then attach each employer's other
openings to every row.
The cap is applied to the aggregated results, after every search has finished and duplicates are gone — so which jobs survive doesn't depend on the order your keywords happened to run in. Jobs dropped by the cap don't count against Max Items: ask for 40 with a cap of 2 and you get 40 jobs spread across more employers, not 24.
Company ratings & reviews (optional)
fetchCompanyRatings adds each employer's public reputation data:
| Field | Meaning |
|---|---|
companyRating / companyReviewCount | Star rating and how many reviews it is based on |
companyCeoApprovalPercent | Share of reviewers approving of the CEO |
companyHeadquarters | Head-office address |
companySectors | Business sectors, in the site's language |
Fetched once per company and cached, so an employer with 30 jobs costs one request.
This one field group needs a residential proxy. Everything else in this Actor talks to an endpoint that does not block datacenter IPs. Ratings come from Indeed's company pages, which are Cloudflare-protected — on datacenter proxy they return
HTTP 403and the fields stay empty. Blocking is IP-reputation based, so it is best-effort even on residential: the same page can return data on a fresh IP and 403 on a heavily used one. The run log states exactly what happened (Reasons: 3x HTTP 403) rather than failing silently.
Coverage is partial regardless of proxy — many smaller employers have no Indeed company page at all. Those keep the fields empty rather than being guessed at.
It is off by default, so the standard path stays on cheap datacenter proxy. Turn it on only when you specifically need reputation data.
More jobs from the same company
A keyword search only shows you the one role that matched. Turn on fetchMoreCompanyJobs
and the actor goes back to each employer it found and pulls the rest of their open positions — so a
search for java developer at a company also surfaces their Staff Data Engineer, IT
Engineering Manager and Associate Project Manager postings.
Useful when the employer matters more than the exact job title: sizing up how much a company is hiring, finding the right opening at a company you already care about, or spotting teams that are scaling.
Companies are matched on Indeed's company ID (the /cmp/… page), never on the name. A
search for "Delta" would otherwise return Delta Dental, Delta Defense and Delta College as
if they were one company; matching on the ID means every expanded row is provably the same
employer. Every employer in your results is expanded, so enrichment is consistent across the
whole dataset.
Output shape
expandedJobsOutput controls how the extras reach you.
nested (default) — your dataset stays one row per search result. Ask for 25 software
engineer jobs and you get 25 rows, every title relevant to what you searched. Each row gains:
| Field | Meaning |
|---|---|
otherJobsAtCompany | List of that employer's other openings |
otherJobsAtCompanyCount | How many are in the list |
Nested entries are compact — jobKey, title, location, salary, datePosted, url —
so rows stay readable rather than repeating a full record inside every one:
{"title": "Lead SFDC Developer","company": "Qualys","otherJobsAtCompanyCount": 3,"otherJobsAtCompany": [{ "title": "Public Sector Channel Manager", "location": "Washington, DC", "url": "https://www.indeed.com/viewjob?jk=daabae56cef01692" },{ "title": "ETM Sales Specialist", "location": "North Carolina", "url": "https://www.indeed.com/viewjob?jk=83a94ca802c5279f" }]}
separateRows — every extra job becomes its own full row, tagged foundVia: "company-expansion"
and expandedFromCompany. Better for feeding a job board, but your dataset will contain
titles unrelated to your search, and these rows are additional to maxItems — asking
for 5 with 2 per company returns 11 rows. In nested mode the row count always equals
maxItems.
Results are deduplicated across both phases, so a job already returned by your search is never repeated. Employers with no Indeed company page (some third-party postings) can't be verified by ID and are skipped rather than guessed at.
Expansion finds jobs that are in Indeed's search index. An employer's
/cmp/page can occasionally list a posting that the search index does not carry, and that job cannot be reached through the API.otherJobsAtCompanyCount: 0therefore means "no others the API can see", not necessarily "no others exist".
Recruitment agencies (AI)
recruitmentAgencyHandling reads each employer's company description with an LLM and decides
whether they're a staffing agency, headhunter or temp firm hiring on behalf of others — as
opposed to a company hiring for itself. Three settings:
| Mode | Behaviour |
|---|---|
off (default) | No classification, no AI calls, no cost |
flag | Classify and label every job, but keep them all |
exclude | Classify and drop agency jobs from the dataset and the webhook |
Use flag when you'd rather filter downstream (in Clay, a spreadsheet, your own code), and
exclude when you want a clean dataset of direct employers only.
A free name / domain / industry screen runs first. Before any AI call, the employer's name, domain and industry
are checked against staffing keywords (staffing, recruit*, talent, personnel,
manpower, workforce, placement, employment, hire, job, staff, temp) plus an
explicit list of the large agencies whose names give nothing away — Robert Half, Aerotek,
TEKsystems, Randstad, Adecco, Insight Global, Kforce, Apex Systems, Vaco and others. A hit
costs zero tokens, and the screen works without an OpenAI key at all: leave the key blank
and you get keyword filtering for free, with a warning that ordinary-looking agencies will
slip through.
Every token is matched as a separate word, never as a fragment inside another word — that
distinction is the whole game. Jobst is not job, Cheshire is not hire, Staffordshire
is not staff; all three are real employers that fragment matching wrongly excluded in
testing. Domain labels have no spaces, so hyphens are treated as the separator
(bright-hire.io → standalone hire, cheshire.org → not a match). Long unambiguous tokens
like staffing and recruitment may still match inside a concatenated label, so
acmestaffing.com is caught. recruitmentAgencyMatchedBy records which rule fired — name,
domain, known-agency or ai — so you can audit real runs and tune the lists.
One call per company, not per job. Verdicts are cached against the employer's Indeed
company ID — the same /cmp/ key the company expansion uses — so an employer with 30 postings
costs one call and all 30 rows get the same verdict. A 20-job run used 6 calls across 7
unique companies, with 11 cache hits.
Every classified row carries:
| Field | Meaning |
|---|---|
isRecruitmentAgency | true / false, or null when it couldn't be judged |
recruitmentAgencyMatchedBy | Which rule decided: name, domain, industry, known-agency or ai |
recruitmentAgencyReason | The model's short justification |
Where the line is drawn. The model is asked about a company's primary business, not whether staffing is mentioned anywhere. Pure agencies go out — Robert Half's profile reads "talent solutions… contract talent and permanent placement". Consultancies, system integrators and outsourcing firms stay in, even when they list staffing among their services — Stefanini's profile says "managed services, systems integration, consulting and strategic staffing", and it is kept as a direct employer. Use
flagmode first if you want to see how these borderline firms are being judged before you letexcluderemove them.
The AI is only as good as the description. This filter reads the company description on Indeed — nothing else. It works well when a company states plainly what it does ("we connect job seekers with employers", "permanent placement and contract talent"). It is unreliable when the description is vague, marketing-heavy, or silent about the business model, and it cannot classify at all when there is no description, which is the case for a large share of smaller employers. Run
flagmode first and read the verdicts before you letexcludedelete anything.
Nothing is dropped on a guess. Employers with no company description on Indeed — often a quarter of them, mostly smaller firms — are kept with
isRecruitmentAgency: null, as are companies whose API call failed after 3 retries. Inexcludemode a row is only ever removed on a positive verdict, so the filter may let an agency through but will not delete a real employer.
Requires your own
openaiApiKey, billed to your OpenAI account. Default model isgpt-5-nanoat $0.05 / 1M input tokens — the cheapest OpenAI offers and ample for a yes/no call.gpt-4.1-nano($0.10) andgpt-4o-mini($0.15) are selectable. Setting a mode other thanoffwithout a key fails the run immediately rather than silently returning unclassified data.
Webhook delivery
Set webhookUrl and every job is POSTed to you the moment it's scraped — one HTTP request
per job, with that job as the entire JSON body. Nothing to unwrap, no array to split, and
no need to fetch the dataset afterwards. That is the shape Clay, n8n, Make and Zapier expect: each
call fires one workflow run with the job fields already at the top level.
POST https://your-endpoint.example/hookContent-Type: application/json{ "jobKey": "4896c534ed0b3699", "title": "Web Developer", "company": "...", ... }
The dataset is always written first, so a webhook can never cost you data. Failed calls
retry 3 times with backoff; if they still fail the job is logged and the run carries on, with
a summary at the end (Webhook: 12 item(s) delivered, 2 failed…). A dead endpoint never
fails the run.
One request per job means a 1,000-job run makes 1,000 calls. If your endpoint rate-limits, keep
maxItemsmodest or make sure it responds quickly — the actor waits for each call.
Example input
{"country": "us","keywords": ["software engineer"],"locations": ["New York"],"radius": 25,"maxItems": 200}
With company expansion — 200 rows, each carrying up to 5 of that employer's other openings:
{"country": "us","keywords": ["software engineer"],"locations": ["New York"],"maxItems": 200,"fetchMoreCompanyJobs": true,"moreJobsPerCompany": 5,"expandedJobsOutput": "nested"}
Output
One row per job — 64 fields, grouped so related columns sit together: job details, job location, salary, company details, then search metadata.
{"jobKey": "51658413c5757f03","title": "Manager, Back-End Engineer","url": "https://www.indeed.com/viewjob?jk=51658413c5757f03","originalApplyUrl": "https://jobs.publicisgroupe.com/job/...","refNum": "R439939","datePublishedClean": "13-06-2026","dateOnIndeedClean": "13-06-2026","expirationDateClean": null,"datePublished": 1781672400000,"datePublishedFormatted": "2026-06-13T09:00:00.000Z","dateOnIndeed": 1781672400000,"dateOnIndeedFormatted": "2026-06-13T09:00:00.000Z","expirationDate": null,"expirationDateFormatted": null,"daysToAppearOnIndeed": 0.2,"expired": false,"urgentlyHiring": false,"highVolumeHiring": false,"language": "en","attributes": ["Azure", "Systems integration", "AI models"],"attributesWithKeys": [{ "key": "75GKK", "label": "Part-time" }],"occupations": ["Software Engineers"],"occupationsWithKeys": [{ "key": "5HC3Z", "label": "Software Engineers" }],"description": "Full plain-text job description...","descriptionHtml": "<div>Full HTML description...</div>","jobLocationFull": "16430 East Hughes Drive, 10014 New York, NY, United States","jobLocationShort": "New York, NY","jobLocationStreet": "16430 East Hughes Drive","jobLocationCity": "New York","jobLocationState": "NY","jobLocationPostalCode": "10014","jobLocationCountry": "United States","jobLocationCountryCode": "US","jobLocationLatitude": 40.72,"jobLocationLongitude": -74.0,"salaryFormatted": "$125,424 - $158,067 per year","salaryMin": 125424,"salaryMax": 158067,"salaryMinPerYear": 125424,"salaryMaxPerYear": 158067,"salaryPeriod": "per year","salaryUnit": "YEAR","salaryCurrency": "USD","company": "Publicis Groupe","companyIndeedUrl": "https://www.indeed.com/cmp/Publicis-Groupe","companyWebsite": "https://www.publicisgroupe.com","companyDomain": "publicisgroupe.com","companyAddressFull": "Graafsebaan 65, 5248 JT Rosmalen","companyRating": 3.8,"companyReviewCount": 178,"companyCeoApprovalPercent": 59,"companySectors": ["Advertising and Marketing"],"companyIndustry": "Internet and Software","companyIndustryRaw": "Iv1_INTERNET_AND_SOFTWARE","companyEmployeeRange": "10,000+","companyRevenue": "$1B+ (USD)","companyCeoName": "Arthur Sadoun","companyCeoPhoto": "https://.../ceophoto.png","companyBriefDescription": "Short one-line pitch from the employer's Indeed profile.","companyDescription": "The employer's full 'about' text — typically several times longer.","companyLogo": "https://.../logo.png","companyHeaderImage": "https://.../header.png","companyLinks": {"corporateWebsite": "https://www.publicisgroupe.com","facebook": "https://www.facebook.com/publicisgroupe","twitter": "https://twitter.com/PublicisGroupe","instagram": "https://www.instagram.com/publicisgroupe"},"keyword": "software engineer","country": "us","foundVia": "search","expandedFromCompany": null,"otherJobsAtCompany": [],"otherJobsAtCompanyCount": 0}
Field notes
| Field | Notes |
|---|---|
jobLocationFull | Where the job is, as one complete address — street, postcode, city, state, country |
companyAddressFull | The employer's head office, which is often nowhere near the job. Needs fetchCompanyRatings |
salaryFormatted | Read-at-a-glance pay, e.g. $70,000 - $90,000 per year, €18.50 per hour. null when the listing states no salary |
salaryMinPerYear / salaryMaxPerYear | Annualised, so hourly and monthly jobs sort against yearly ones. Assumes 40h/week, 52 weeks |
companyIndeedUrl | The employer's Indeed company page (/cmp/…). Stable unique company ID |
companyIndustry | Readable industry label, normalised from Indeed's internal enum |
attributesWithKeys / occupationsWithKeys | Indeed's stable keys plus labels. Labels are translated per country ("Part-time" / "Parttime") so only keys match reliably across your 15 sites |
language | Language the listing is written in, e.g. nl |
expirationDate | When the posting expires — often null, as Indeed rarely publishes it |
companyRating … companySectors, companyAddressFull | Only when fetchCompanyRatings is on; see the caveat above |
companyIndustryRaw | The untouched enum (Iv1_INTERNET_AND_SOFTWARE), kept for exact matching |
*Clean dates | Plain calendar date, DD-MM-YYYY, in UTC — datePublishedClean, dateOnIndeedClean, expirationDateClean |
datePublished / dateOnIndeed | Epoch milliseconds; the *Formatted twins are ISO 8601 |
daysToAppearOnIndeed | Days between the employer publishing the job and it appearing on Indeed. Under 1 = posted fresh; several days = an older listing re-surfacing. See below |
originalApplyUrl | The employer's own apply link when they have one — Indeed-hosted when they use Indeed Apply |
foundVia | "search" (matched your keyword) or "company-expansion" |
expandedFromCompany | Which employer a row was expanded from; null for search rows |
otherJobsAtCompany | Other openings at this employer — populated only when company expansion is on |
highVolumeHiring / urgentlyHiring | Indeed's own hiring-demand flags |
Telling fresh jobs from recycled ones. Indeed's API has an isRepost field, but it returns null on every job in every country tested (150 jobs across NL and US), so it is not included — a permanently empty column is worse than none. Instead you get daysToAppearOnIndeed, the measured gap between the employer publishing the role and Indeed listing it. In a 60-job US sample, 56 appeared the same day and 4 took between 1.9 and 9.1 days. Filter on it yourself: < 1 for genuinely fresh postings, > 1 for listings that were already sitting elsewhere. It is deliberately not labelled "repost", because a long gap can also mean Indeed simply indexed the job late.
Renamed fields. Job location columns are now prefixed
jobLocation*so they can never be mistaken for the employer's own address. If you map these downstream, update:location→jobLocationFull,city→jobLocationCity,state→jobLocationState,postalCode→jobLocationPostalCode,countryName→jobLocationCountry,latitude→jobLocationLatitude,longitude→jobLocationLongitude.
Rich company + application data — every listing includes the employer's CEO (name + photo), social links (website, Facebook, Twitter, Instagram), the full company description (companyDescription, alongside the shorter companyBriefDescription), and the direct employer apply URL (originalApplyUrl), not just the Indeed page.
Company fields (
companyIndustry,companyRevenue,companyEmployeeRange, both descriptions, CEO, logo) come from the employer's own Indeed profile and arenullwhen they haven't filled one in. Smaller employers frequently have none of them.
Use cases
- Recruiting & sourcing — monitor open roles by market, skill and company, with agencies filtered out so you only see employers hiring directly
- Lead generation — company domains, websites, CEO names, full descriptions and social profiles, pushed straight into Clay or your CRM by webhook
- Account mapping — find one job at a target company, then pull every other role they have open to see which teams are growing
- Market research — salary ranges, demand trends, hiring volume by industry and region
- Job boards — feed fresh, fully-detailed listings into your product with descriptions already included
Reliability
An invalid or unauthorised OpenAI key stops the AI filter immediately rather than retrying — the cause is reported once, the free name/domain screen carries on, and every job is kept rather than dropped, so a bad key costs you nothing but the filtering.
Companies are classified five at a time rather than one by one — on a 37-company run that is 8.9s instead of 27.8s, and it stays well inside OpenAI's rate limits. Every API call retries up to 6 times with a fresh proxy IP and increasing backoff, covering both HTTP errors and transport-level failures (dropped sockets, proxy ECONNRESET, timeouts). If one search still can't complete, that keyword is abandoned and the run delivers everything the other searches collected, listing what failed in the log. A run only fails outright when every search was unreachable.
Notes
- Apify Proxy must be enabled, but only the cheap datacenter tier. Apify blocks direct outbound network access for Actors running under limited permissions, so requests have to route through the proxy. The endpoint this Actor uses does not block datacenter IPs, so there is no reason to pay residential rates.
- Some fields (industry, revenue, website) are only present when Indeed has them on the employer's profile.
- Export as CSV, Excel, or JSON from the dataset tab, or via the Apify API.