Company Jobs Scraper — Career Pages by Domain, 9 ATS Boards avatar

Company Jobs Scraper — Career Pages by Domain, 9 ATS Boards

Pricing

from $0.80 / 1,000 jobs

Go to Apify Store
Company Jobs Scraper — Career Pages by Domain, 9 ATS Boards

Company Jobs Scraper — Career Pages by Domain, 9 ATS Boards

Paste a company domain and get every live opening from its Greenhouse, Lever, Ashby, Workday or other ATS board — nine platforms, one box, no board tokens needed. From $0.80 per 1,000 jobs. Never pay for a domain we couldn't resolve.

Pricing

from $0.80 / 1,000 jobs

Rating

0.0

(0)

Developer

Steadyfetch Team

Steadyfetch Team

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

20 hours ago

Last modified

Share

Company Jobs Scraper — Greenhouse, Lever, Workday, Ashby Boards

Never pay for a domain we couldn't resolve. Paste a company domain — we find its Greenhouse, Lever, Ashby or Workday board and return every live opening. Nine ATS platforms, one box — no board tokens needed.

From $0.80 per 1,000 jobs — all-inclusive pay per event, nothing charged for starting a run, charged only on delivery.

Running the same domains again? An opening already delivered to your account is never charged a second time — see Never pay for the same opening twice.

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-opening sample — vercel.com's own hiring board — charged like any run, under a cent. Set only the filters — a posted-within window, remote-only, a row cap — and no companies, 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 domains already in the form and you get those companies for real, up to your row caps.

Company Jobs Scraper input form in the Apify console: company domains, hiring-platform filter, posted-within and a remote-only toggle

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 real row

Five live openings from a real run, in the Apify console's dataset table — the company's own board, the department, the posted date and both the job and apply links, one row per opening:

Dataset table of a real Company Jobs Scraper run: five Vercel Account Executive openings read from the company's Greenhouse board, with department, posted date, job URL and apply URL

And one delivered row in full:

{
"source": "greenhouse",
"sourceJobId": "6136160004",
"jobUrl": "https://job-boards.greenhouse.io/vercel/jobs/6136160004",
"applyUrl": "https://job-boards.greenhouse.io/vercel/jobs/6136160004",
"title": "Account Executive, Commercial",
"company": "Vercel",
"companyDomain": "vercel.com",
"location": { "raw": "Hybrid - London", "city": "Hybrid - London", "region": null, "postcode": null, "country": null },
"isRemote": null,
"employmentType": null,
"department": "Account Executive",
"salary": null,
"postedAt": "2026-08-06T16:50:10.000Z",
"updatedAt": "2026-08-18T22:06:19.000Z",
"scrapedAt": "2026-08-27T10:00:00.000Z",
"descriptionText": "About Vercel:\n\n Vercel is the agentic infrastructure company. We free people and agents to ship what's next…",
"descriptionHtml": "<div class=\"content-intro\"><h2>About Vercel:</h2>…",
"atsPlatform": "greenhouse",
"atsBoardToken": "vercel",
"resolvedVia": "careers-link",
"dedupeKey": "greenhouse:vercel:6136160004",
"charged": true,
"missReason": null
}

Plus one company row per domaindomain, companyName, atsPlatform, boardUrl, openRoleCount, sharedRoleCount (openings already delivered — and charged once — under another company in the same run), totalOpenRoles, resolved, resolvedVia — and one summary row with delivered, stoppedBy, resumeCursor, datasetId (paste it into resumeFromDatasetId to continue this run later), carriedOver and chargedEvents.

See a whole run's output before you spend anything: real dataset from a verified run — six live openings from vercel.com and linear.app, their two company rows, and the run summary, exactly as this actor returned them.

fieldnotes
title · company · companyDomaincompany is filled when the board publishes it
location{raw,city,region,postcode,country} · isRemoteas the board states it — country is an ISO-2 or null, never a state or a postcode
employmentType · department
salary{min,max,currency,period,isEstimate}null unless the company published a range
postedAt · updatedAt · scrapedAtISO 8601; null when the board only gives relative text
descriptionText · descriptionHtmlfull description where the board's API includes it
jobUrl · applyUrlthe company's own posting, not a re-hosted copy
atsPlatform · atsBoardToken · resolvedViahow we found the board
charged · missReason · dedupeKeythe reconciliation trio

Agent / API paste-block

Actor: steadyfetch/company-jobs-by-domain
Required: domains (array of company domains, careers URLs, board URLs or names)
Optional: atsFilter (array: greenhouse | lever | ashby | workday | workable |
smartrecruiters | rippling | personio | teamtailor)
remoteOnly (boolean, default false — keep only openings the board
marks remote; filtered openings are never charged)
postedWithinDays (integer 1-30 — keep only openings posted that recently;
filtered openings are never charged)
maxItems (integer, default 1000 — hard cap for the whole run)
maxItemsPerCompany (integer, default 200)
maxRunSeconds (integer, default 900 — clean stop, never a timeout kill)
includeSeen (boolean, default false — OFF: an opening 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 opening already
in it is skipped and never charged again)
Charges: job once per delivered opening
company-checked once per company with a definitive answer (including a
verified zero-openings result)
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.
Build spec: https://apify.com/steadyfetch/company-jobs-by-domain/api
Token: https://console.apify.com/settings/integrations
curl -X POST "https://api.apify.com/v2/acts/steadyfetch~company-jobs-by-domain/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"domains":["stripe.com","linear.app"],"maxItemsPerCompany":50}'

Hiring platforms

Greenhouse · Lever · Ashby · Workday · Workable · SmartRecruiters · Rippling · Personio · Teamtailor. Each is read from that platform's own public job-board API, so the data is whatever the company published, unchanged.

How a domain becomes a board (resolvedVia tells you which one happened):

resolvedViawhat it means
board-urlyou pasted the board URL — no lookup needed
careers-linkthe company's own careers page links its board
token-patternthe domain name matched a board token and that board returned live openings
name-probesame, from a company name instead of a domain

A board found by guessing the token only counts when real openings come back. An empty board reached by guesswork is not a match, so it is never charged.

What you are never charged for

situationmissReasonrun status
No hiring board found for the domainUNRESOLVED_DOMAINSUCCEEDED — the row lists every platform checked
The board rate-limited or dropped usTHROTTLEDSUCCEEDED — re-runnable, and the row says so
The board answered with an access challengeCHALLENGEDSUCCEEDED — re-runnable
The board was unavailable this runSOURCE_UNAVAILABLESUCCEEDED — re-runnable
The board answered and listed nothingCLEAN_EMPTYSUCCEEDED
A domain, URL or name we could not readUSER_INPUTSUCCEEDED — the row names the fix
A hiring platform name we do not recognise in "Only these hiring platforms"USER_INPUTSUCCEEDED — the row names it; the run stops rather than bill you for every platform
The note on a run where you set filters but no companies, so the sample ran under themSAMPLE_SETTINGS_KEPTSUCCEEDED — the row names which settings were yours
An opening your account already has from an earlier runskipped, never charged twice
Your own maxItems / maxRunSeconds / cost capSTOPPED_AT_LIMITSUCCEEDED — summary row with resumeCursor and the datasetId to continue from

The one exception: COMPANY_NO_OPENINGS charges the company-checked event and no job rows. Confirming a company is not hiring is a real answer, and it is the answer that costs you the least.

We never guess. A rate limit is reported as a rate limit, not as "this company has no board" — a temporary problem is never dressed up as a permanent verdict.

This actor may fail when a hiring platform changes things. The two you are most likely to see are UNRESOLVED_DOMAIN (we checked every platform and found no board) and THROTTLED (a board rate-limited us). Both ship as a row with charged: false, the run still SUCCEEDS, and the row says whether re-running can help.

Never pay for the same opening twice

Every run remembers the openings it delivered for your account, in a key-value store called company-jobs-watch-account in your own Apify account. Run the same domains 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; each company's own row says how many of its openings you already had.

The company answer is different on purpose: "this board has N live openings today" is a fresh fact about that company right now, so it is delivered and charged on every run — that is what you are asking for when you re-run a domain.

Want one complete dataset per run instead of only what changed? Turn on Include openings 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 an opening you collected last quarter is collected (and charged) again if it is still live.
  • Only delivered rows are remembered. An unresolved domain, a throttled board or a board that listed 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.

Filters you can set, and what they cost you

Only these hiring platforms is a hard filter on where we look. The console's dropdown and Apify's own input check refuse a name we do not know before a run starts; if one ever reaches the actor another way, the run ships one uncharged row naming it and stops — it is never quietly ignored, because that would read as "check all of them" and bill you for exactly the boards you asked to leave out. A single name works as well as a list ("greenhouse" or ["greenhouse"]), and capitalisation does not matter.

remoteOnly and postedWithinDays narrow what is delivered, and an opening they leave out is never charged. They lean on what each board actually publishes: Greenhouse and Workday publish no remote flag at all, and a few boards publish no posting date. An opening we cannot check against your filter is left out too — and the company's own row says how many, so a filter can never read as "this company has no openings". A board emptied by your filters keeps missReason: null for exactly that reason.

Limits are hard limits

maxItems, maxItemsPerCompany and maxRunSeconds stop the run exactly where you set them. The run still finishes successfully, the summary row names which limit bound, and resumeCursor lists the companies that were not reached. A time limit ends the collecting, never the delivering: a board already fetched when the clock runs out is still delivered in full, so a run never pays for openings it does not hand you. To collect the rest without paying again for what you already have, re-run the same input with a higher cap and resumeFromDatasetId set to that run's dataset ID (it is on the summary row as datasetId): every opening 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.

And they hold across a restart. If the platform moves your run to another server mid-way — or you resurrect it yourself — the run reads back what it already delivered and picks up from there: no duplicate rows, no second charge for an opening you already have, and your row cap still means what it says. Resurrecting a run that already finished costs nothing and returns the same dataset.

FAQ

The company isn't on any ATS — what now? The company row lists every platform that was checked. If you know the board URL, paste it directly (https://jobs.lever.co/acme) and we skip the lookup entirely.

Can I search jobs across boards instead of by company? Not with this actor — it answers "is this company hiring, and for what". A job board URL (LinkedIn, Indeed, Glassdoor) is rejected with a guidance row rather than treated as an employer.

How do I chain this after a company-list scraper? Feed the domains array from any actor that outputs company domains. Same-company duplicates across input shapes are delivered once and charged once — the later company's row reports them in sharedRoleCount instead of claiming them again.

Why is company sometimes empty? Several board APIs do not publish the employer name in their listing payload. We leave the field null rather than invent one; companyDomain and the company row always identify it.

Does {} cost anything? Yes — like any run, and under a cent. With no input at all it reads one real hiring board (vercel.com's) and returns up to 5 openings, and you pay for the openings it delivers. If that board cannot be reached at that moment you get one uncharged company row saying so, never an empty result.


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 "every open role on stripe.com's careers page" and point it at this actor.

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

{
"mcpServers": {
"company-jobs": {
"url": "https://mcp.apify.com?tools=steadyfetch/company-jobs-by-domain",
"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)this actor
Glassdoor, with the employer ratingGlassdoor Jobs Scraper — Employer Star Rating on Every Row
Several boards at once, de-duplicatedJob Board Scraper — Indeed + Career Sites, One Deduped Feed
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.