Job Board Scraper — Boards, Career Pages, Listings Aggregator avatar

Job Board Scraper — Boards, Career Pages, Listings Aggregator

Pricing

from $1.80 / 1,000 job listings

Go to Apify Store
Job Board Scraper — Boards, Career Pages, Listings Aggregator

Job Board Scraper — Boards, Career Pages, Listings Aggregator

One keyword across Indeed and any company's own hiring board, merged and de-duplicated into one feed — the same role on two boards is one row and one charge. From $1.80 per 1,000 job listings. Blocked boards, empty searches and merged duplicates are never charged.

Pricing

from $1.80 / 1,000 job listings

Rating

0.0

(0)

Developer

Steadyfetch Team

Steadyfetch Team

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

15 hours ago

Last modified

Share

Never pay for a job we didn't deliver. One keyword across Indeed and any company's own hiring board, merged and de-duplicated into one feed. The same role found on two boards is one row — and one charge.

From $1.80 per 1,000 job listings — all-inclusive pay per event, nothing charged for starting a run, charged only on delivery. One price for the whole feed: no per-source fee, no per-search fee.

Just want to see it work? Run it with no input at all ({} from the API or an AI agent) and you get a real 5-job sample — "software engineer" on Indeed US plus vercel.com's own board, merged — charged like any run, a cent or two at most. Set only the filters — a country, a sort order, a row cap — and nothing to search for, and that same sample runs under them, charged like any run; a limit larger than the sample's own is capped at it. In the console, Start with the example keyword already in the form and you get that search for real, up to your row cap.

Running the same search again? A job already delivered to your account is never charged a second time — see Never pay for the same job twice.

Job Board Scraper input form in the Apify console: search keyword, Indeed countries, location and radius, with the company boards, sources and limits sections

Unofficial. This actor is not affiliated with, endorsed by, or sponsored by Indeed or any hiring-board provider. "Indeed" is a trademark of Indeed, Inc.; Greenhouse, Lever, Ashby, Workday, Workable, SmartRecruiters, Rippling, Personio and Teamtailor are trademarks of their respective owners. It reads only the public search and hiring pages an ordinary browser sees.

Every row carries charged and missReason, so you can reconcile the invoice from the dataset itself without opening the console. Only rows with charged: true were billed.


Output — a merged row

Five merged jobs from a real run, in the Apify console's dataset table — every row shows which boards it came from, how many duplicates it absorbed, and the department, type and posted date:

Dataset table of a real Job Board Scraper run: five merged listings across Indeed and Greenhouse showing duplicate count, sources, employment type, department, posted date, primary source and description

And one merged row in full:

{
"title": "Senior Backend Engineer",
"company": "Acme",
"location": { "raw": "Berlin, Germany", "city": "Berlin", "region": null, "postcode": null, "country": "DE" },
"salary": { "min": 85000, "max": 115000, "currency": "EUR", "period": "year" },
"descriptionText": "…full description from the company's own board…",
"source": "greenhouse",
"sources": [
{ "board": "greenhouse", "jobUrl": "https://job-boards.greenhouse.io/acme/jobs/123", "applyUrl": "…", "postedAt": "2026-08-24T09:00:00.000Z", "salary": null },
{ "board": "indeed", "jobUrl": "https://www.indeed.com/viewjob?jk=…", "applyUrl": null, "postedAt": null, "salary": { "min": 85000, "max": 115000, "currency": "EUR", "period": "year" } }
],
"merged": true,
"duplicateCount": 1,
"dedupeConfidence": "high",
"dedupeKey": "mb:…",
"charged": true,
"missReason": null
}

Plus one coverage row per source and per company board (source, board, status, delivered, resolved, missReason) and one summary row with delivered, uniqueFound, duplicatesMerged, stoppedBy, datasetId (paste it into resumeFromDatasetId to continue this run later), carriedOver and chargedEvents.

See a real run's full output → — eight live jobs pulled from four different company boards in one run, plus the coverage row for each board and the summary. Every charged row is marked charged: true; nothing else is.

Agent / API paste-block

Actor: steadyfetch/multi-job-board-scraper
Required: query (job title/keyword, or a full Indeed search URL)
Optional: companies (array of company domains / careers URLs / board URLs / names)
sources (array: ["indeed","ats"], default both)
countries (array of ISO-2, default ["US"] — each is its own Indeed search)
location (string, e.g. "Austin, TX")
radiusKm (integer 1-160, needs a location — Indeed only)
dedupe (boolean, default true — merge the same role across boards)
postedWithinDays (integer, 1-30)
sortBy (relevance | date, default relevance — Indeed only; the
option's own name works too, e.g. "Date posted")
remoteOnly (boolean, default false — filtered rows are never charged)
maxItems (integer, default 100 — hard cap on UNIQUE jobs)
maxItemsPerCompany (integer, default 200)
maxRunSeconds (integer, default 1200 — clean stop, never a timeout kill)
includeSeen (boolean, default false — OFF: a job already delivered to your
account is skipped, uncharged. ON: it comes back marked
repeat:true with firstSeenRunId, still uncharged)
resumeFromDatasetId (string — an earlier run's dataset ID; every job already in it
is skipped and never charged again)
Charges: job-listing once per UNIQUE job after de-duplication
Note: sending null (or an empty value) for an optional field is the same as leaving
it out — the default is used, so templated callers need no special-casing.
Every number above falls back to its default if it arrives empty — never to
its smallest value.
Token: https://console.apify.com/settings/integrations
curl -X POST "https://api.apify.com/v2/acts/steadyfetch~multi-job-board-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"query":"backend engineer","companies":["stripe.com","https://jobs.lever.co/leverdemo"],"countries":["US"],"maxItems":50}'

What it does

You give one keyword and, optionally, the companies you care about. The actor:

  1. searches Indeed for your keyword (across the countries you pick), and
  2. pulls each company's own hiring board — Greenhouse, Lever, Ashby, Workday, Workable, SmartRecruiters, Rippling, Personio or Teamtailor — filtered to roles that match your keyword, then
  3. merges the same role found on more than one board into a single row, keeping every source link, and charges you once.

Company boards are how you reach roles that never appear on a job aggregator. About half of companies resolve from a bare domain (mid-market best; large enterprises mostly run their own Workday/Oracle, so paste a board URL for those). You are never charged for a company we cannot resolve.

The de-duplication, in plain terms

A "duplicate" is the same role — same company, same title, in an overlapping location — found on two different sources. When we find one, you get one row with a sources array listing every board it was on, duplicateCount, and a dedupeConfidence of high (matched on a shared city) or medium (matched on a country-wide or remote posting).

We tuned this on a corpus of real jobs to never delete a distinct job. When two roles at one company genuinely can't be told apart from an aggregator's single row, we keep them separate — tagged with a shared dedupeGroupId — rather than merge them and silently drop one. A wrong merge is a job you never see, so we always err on the side of showing you more.

Turn dedupe off and each board's copy is its own row (the identical posting fetched twice is still never charged twice).

Sources

  • Indeed — needs a keyword. United States, United Kingdom and Germany are verified end to end; any listing that comes back outside the country you asked for is reported, never charged.
  • Company boards (ATS) — need company domains. Greenhouse, Lever, Ashby, Workday, Workable, SmartRecruiters, Rippling, Personio, Teamtailor — each the company's own documented public board.

Narrow with sources (for example ["ats"] to skip Indeed). Google Jobs is deliberately not a source — its jobs tab returns only ~10 rows per query with no way to page deeper, so it would add cost without adding coverage.

What you are never charged for

situationrun status
A board answered with an access check or rate limitSUCCEEDED — re-runnable, the coverage row says so
A company we could not resolve to a boardSUCCEEDED — uncharged, lists what was tried
A source ran and matched nothingSUCCEEDED — a real answer
A duplicate merged into another rowSUCCEEDED — charged once, not per board
A job your account already has from an earlier runSUCCEEDED — skipped, never charged twice
An input we could not readSUCCEEDED — the row names the fix
The note on a run where you set filters but nothing to search for, so the sample ran under themSUCCEEDED — the row names which settings were yours
Your own maxItems / maxRunSeconds / cost capSUCCEEDED — summary row names the knob and the datasetId to continue from

We never guess. An access check is reported as an access check, not as "no jobs found" — a temporary problem is never dressed up as a permanent verdict, and it is never charged.

This actor may fail when a board changes things. The two you are most likely to see are a board answering with an access check or rate limit, and a company we could not resolve to a hiring board. Both ship as a coverage row with charged: false, the run still SUCCEEDS, and the row says whether re-running helps.

When Indeed turns every connection away and the run still has time, it does not stop at the first refusal: it varies the connection type and device shape, reaches a rendered fallback, and then waits a couple of minutes and tries the whole ladder again — up to three times. How much of the run's remaining time one blocked search may spend waiting follows the size of what you asked for: a third of it while there are still other searches and company boards to fetch, so a wide block leaves them their share, and up to all of it when this search is the only thing left to fetch. Only then does the uncharged coverage row ship, and it says what was tried: how many connections, over how many minutes, whether it waited, and how much time was left. A run with a short time limit makes one attempt and reports at once; a "not found" answer is answered at once and never waited on.

Never pay for the same job twice

Every run remembers the jobs it delivered for your account, in a key-value store called multi-job-watch-account in your own Apify account — under every identity a job carries, one per board that saw it. Run the same search again and what you already have is skipped before it takes a row slot: no row, no charge, and no bite out of your row cap. The status line says how many (18 already in your account, skipped, not charged) and OUTPUT.skippedSeen holds the number.

Storing every identity is what makes it work here: a role you first got from Indeed alone is still recognised next week when a company board sighting merges with it, and the other way round — so a merged row can never read as new and bill you again.

Want one complete dataset per run instead of only what changed? Turn on Include jobs you already have (includeSeen: true) and those rows come back anyway — marked repeat: true with firstSeenAt and firstSeenRunId naming the run that first delivered them, charged: false.

  • The store lives in your account, one record (SEEN). Open it in Storage → Key-value stores to see everything the actor has delivered you, or delete the record to start over. This actor runs with limited permissions and touches nothing else in your account.
  • Entries older than 90 days count as new again, so a job you collected last quarter is collected (and charged) again if it is still live.
  • Only delivered rows are remembered. A blocked board, an unresolved company or a source that matched nothing leaves nothing behind, so the next run tries it again.
  • If the store cannot be read on some run, the run still runs: it delivers and charges as usual and the status line says the repeat check was unavailable, so you know a repeat may have been charged that once.
  • The no-input sample is outside all of this. Pressing Start with nothing filled in reads both boards live every time and neither checks nor updates this list, so you always see a real result rather than a hand-back of the last one — and those few sample rows are charged like any run's, each time you press it.

FAQ

I hit my cap — how do I get the rest without paying twice? Re-run the same input with a higher maxItems and resumeFromDatasetId set to that run's dataset ID (it is on the summary row as datasetId): every job already in that dataset is skipped — never fetched, never charged — and the new summary's carriedOver says how many. A dataset ID that cannot be read stops the run before it fetches anything, uncharged. Under a small cap, delivery rotates across boards, so Indeed plus two company boards returns rows from all three rather than the first few Indeed cards. A time limit ends the collecting, never the delivering: every job already collected when the clock runs out is still delivered, so a run never pays for jobs it does not hand you.

Does {} charge anything? Yes — like any run, and a cent or two at most. With no input at all the run is a real 5-job sample — "software engineer" on Indeed US plus vercel.com's own board, merged — and you pay for the jobs it delivers. Set only filters and nothing to search for and the same sample runs under them, charged the same way, with one extra uncharged row naming which settings were yours. A source that does not answer gets an uncharged coverage row saying so, never an empty result.

Can I just scrape a company's whole board? Yes — pass the companies with sources: ["ats"]. For deep, unfiltered board pulls by domain, the sibling Company Jobs Scraper — Career Pages by Domain, 9 ATS Boards is purpose-built for exactly that.

Why did a company return nothing? Either it has no live openings matching your keyword, or its board could not be positively identified from the domain — the coverage row tells you which, and neither is charged. Paste the board URL directly if you know it.


Run it from an AI agent (MCP)

Works out of the box from any MCP client (Claude, Cursor, …) via Apify's MCP server — ask for "remote data engineer jobs across Indeed and company career pages, de-duplicated" and point it at this actor.

Pin this actor in one click — add this server to your MCP config:

{
"mcpServers": {
"job-board": {
"url": "https://mcp.apify.com?tools=steadyfetch/multi-job-board-scraper",
"headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
}
}
}

Steadyfetch jobs suite

One actor per source, one job each, the same row shape — so a pipeline built on one source takes the next without a rewrite. All-inclusive pay per event, nothing charged for starting a run, charged only on delivery. From 16 September 2026 Google Jobs adds one search fee of $0.004 per search that returns listings; a search that returns nothing pays nothing.

SourceActor
IndeedIndeed Jobs Scraper — Salary, Apply Links, Exact Country
A company's own career site (paste a domain)Company Jobs Scraper — Career Pages by Domain, 9 ATS Boards
Glassdoor, with the employer ratingGlassdoor Jobs Scraper — Employer Star Rating on Every Row
Several boards at once, de-duplicatedthis actor
Google Jobs, with the full panel and apply linksGoogle Jobs Scraper — Full Panel, Salary, Apply Links

The rest of the steadyfetch shelf — same contract everywhere: all-inclusive pay per event, nothing charged for starting a run, charged only on delivery (three actors add a small delivery-conditional fee from 16 September 2026: keyword volume's fresh lookup, profile posts' profile lookup, Google Jobs' search fee — each README states it).

FamilyActors
Ad creative intelligenceFacebook · Google Ads video · TikTok · LinkedIn · Google Ads text & OCR
Trends & keywordsGoogle Trends · Trends Now · Breakout keywords · Autocomplete keywords · Keyword volume & CPC · Social trends
YouTube transcriptsYouTube videos · YouTube channels
InstagramReel transcripts · Profile posts
JobsIndeed · Career sites by domain · Glassdoor · Multi-board · Google Jobs
AmazonProducts · Search · Bestsellers · Sellers
Any media fileSpeech to Text · any link or file

Feedback & support

Found an issue? Open it on the Issues tab — we usually reply within a couple of hours, always within a day. Feature requests welcome. If this actor earned its keep, a rating helps other buyers find it, and saving it keeps it one click away in your console.