Company Jobs Monitor: Greenhouse, Lever, Workday, Ashby & More
Pricing
$1.50 / 1,000 job rows
Company Jobs Monitor: Greenhouse, Lever, Workday, Ashby & More
Get only the jobs a company posted since your last run. Reads the public job feeds of Greenhouse, Lever, Ashby, Workable, Recruitee, Workday, Phenom and Eightfold; SmartRecruiters is closed by its host's robots.txt (measured 2026-09-07). Pay per job row; status rows and quiet days are free.
Pricing
$1.50 / 1,000 job rows
Rating
0.0
(0)
Developer
Dockhand
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Watch the companies you care about and get only the jobs they posted since your last check — paid per job row, with every empty result explained for free.
Give this company jobs monitor a list of companies — prospects, competitors, dream employers — and it reads their careers pages through the public job feeds of eight applicant-tracking systems — Greenhouse, Lever, Ashby, Workable, Recruitee, Workday, Phenom and Eightfold — and recognises a ninth, SmartRecruiters, whose feed its host currently closes. The first run returns their current openings; with monitor mode on, every run after that returns only the new postings. Every host it reads — the vendors' API hosts as much as an employer's own careers domain — has its robots.txt read and obeyed first, and one of the nine is currently closed by its own host: api.smartrecruiters.com publishes a robots.txt that disallows every client but LinkedInBot (measured 2026-09-07), so a SmartRecruiters board gets the free not-found row naming that rule, and no request, until the host permits it (see Honest limits). The quickest way to try it: press Start with the prefilled company list and you get a sample of real openings from six boards on six of the nine platforms (Greenhouse, Lever, Ashby, Workday, Phenom, Eightfold) in well under a minute — about 15 seconds on the platform (run vBhYaKgqE2Mwgcx0z, 2026-09-07, 150 rows in 14.8 s; each host costs a robots.txt read and about a second's pause before its feed).
What you get
Job rows, one per opening. company, platform, job_id, title, department, team, location, remote, employment_type, url (the direct apply link), published_at, updated_at, plus description when you ask for it. An empty cell means the source did not publish that field; nothing is filled in with a guess. Which platform fills which column is listed under Which job boards it reads.
Only-new monitor mode. Turn on monitorMode and the actor remembers, per company list, which jobs it has already given you. The next run delivers only postings it has not delivered before. Memory lives in a key-value store named career-jobs-monitor-state on your own Apify account; change the company list and monitoring starts fresh for the new list, or delete that store to reset everything.
Title filters. Keep only titles containing your words (engineer, junior), drop the ones you never want (senior, director). Filtering happens before anything is charged.
A free status row for every company with nothing to show. You never stare at an unexplained empty table. Each of these rows carries company, status and a plain-English note, and none of them is ever charged:
status | What it means |
|---|---|
not-found | No supported job board answered for that name; or the URL is on a platform this actor does not cover; or the host did not answer on this run (after three attempts); or the feed answered with an error; or it answered with a bot-verification challenge instead of its job list (a fact about this run's network, named as such — no workaround is attempted); or the host's own robots.txt disallows the feed path, or could not be read, so the feed was not asked. The row's note says which, and RUN_SUMMARY carries the exact answer as detail with a one-word reason. |
no-open-jobs | The board you named was reached and it lists nothing open right now. |
filtered-out | The board has jobs and none matched your title filters. The note says how many were on it. |
no-new-jobs | Monitor mode: the board was reached and nothing on it is new since your last run. |
charge-limit-reached | This run's maximum cost was reached, or billing itself failed. Jobs held back by the limit were not delivered; if billing itself failed, the rows already written are un-marked instead. Either way they are not marked as seen and arrive next run. |
results-truncated | Paging stopped short of the whole board. The note says whether the SOURCE refused the rest, or this actor's own safety guard or per-board time budget stopped the read, with the numbers behind the claim. |
partial | A request to the source failed twice, so that company's rows are an incomplete picture of its board — or, with descriptions on for a Phenom or Eightfold board, the per-board time budget ran out before every description was fetched, and the note says how many are missing. |
Set includeNotFoundRows to false if you want job rows only.
A RUN_SUMMARY record in the run's key-value store, with one entry per company: the platform detected, the verdict (ok, not-found, no-open-jobs, filtered-out, no-new-jobs or charge-limit-reached), jobs_found — the board's own total, never the number a cap returned; only where the feed states no total and the read was cut short or lost a page does it fall back to the rows actually read, and then results_truncated or partial in the same entry says why that is not the board's size — jobs_fetched and jobs_output, and where they apply source_pages, source_page_max, truncation_reason (with guard — postings or time — when the reason is ours), partial and source_errors. A not-found entry carries detail (the exact answer, e.g. robots.txt disallows /v1/companies/BoschGroup/postings on api.smartrecruiters.com, or the host, attempt count and error of a host that never answered) and reason — one of absent (every platform asked answered "no such board"), walled (a bot-verification challenge; the entry also carries walled: true), disallowed (a robots.txt rule, so the feed was not asked), unreachable (no answer after three attempts, or a robots.txt that could not be read) or error (an HTTP error or a body that was not the feed). At the top level: total_jobs_output, status_rows, sources_ok (companies whose board was reached, with jobs or without), sources_failed (companies with no board), and of those sources_walled, sources_robots_disallowed and sources_unreachable, all_sources_failed, monitorMode, charge_limit_reached, jobs_withheld_charge_limit, results_truncated, partial_results, charge_failed, job_rows_charged and pay_per_event, present on every run.
Which job boards it reads
Nine applicant-tracking systems, all through the public, logged-out JSON feed each one publishes for its customers' careers pages. No page scraping, no login, no proxies, no browser; every host's robots.txt is read and obeyed before its feed is asked, and no host — a vendor's API host or an employer's own domain — is asked more than about once a second.
| Platform | How to name a company | Careers-page URL it recognises |
|---|---|---|
| Greenhouse | bare name, e.g. stripe | boards.greenhouse.io/<company> or job-boards.greenhouse.io/<company> |
| Lever | bare name | jobs.lever.co/<company> |
| Ashby | bare name | jobs.ashbyhq.com/<company> |
| SmartRecruiters | bare name | careers.smartrecruiters.com/<Company> (also jobs. or www.) — currently refused by api.smartrecruiters.com's own robots.txt, so every SmartRecruiters board returns the free not-found row naming the rule (measured 2026-09-07; see Honest limits) |
| Workable | bare name | apply.workable.com/<company> |
| Recruitee | bare name | <company>.recruitee.com |
| Workday | URL only | <tenant>.wd<N>.myworkdayjobs.com/<site>, any page under it |
| Phenom | URL only (or phenom:<host>/<country>/<lang>) | the employer's own careers site, e.g. careers.lilly.com/us/en/search-results, jobs.cvshealth.com/us/en/job/… — any page under its locale path |
| Eightfold | URL only (or eightfold:<host>/<tenant domain>) | <company>.eightfold.ai/careers, or the employer's own site fronting it, e.g. careers.micron.com/careers, jobs.ericsson.com/careers |
A bare name is tried against the six auto-detectable platforms in the order above, and the first board with jobs on it wins; a platform whose host's robots.txt does not permit the feed (SmartRecruiters, as measured on 2026-09-07) is skipped without a request, and a name found on none of the six says so in its RUN_SUMMARY entry (company name not fully probed — 1 of 6 feeds not permitted by the host's robots.txt) rather than claiming an answer it never got. A URL on a platform's own address (boards.greenhouse.io, *.myworkdayjobs.com, *.eightfold.ai, …) goes straight to its platform. A URL on any other host — an employer's own careers domain — is asked whether it is a Phenom or an Eightfold site: that host's robots.txt is read first (one request, once per host per run), then one request to each of the two feeds it permits, in the order the path suggests (/us/en/… is Phenom's shape, /careers is Eightfold's). A host on neither gets the free not-found row with both answers in RUN_SUMMARY; a host whose robots.txt disallows a feed path, or whose robots.txt cannot be read, is not asked that question at all, and its RUN_SUMMARY entry says so. If a probe misses a site you know is there, name the platform yourself with the phenom: or eightfold: prefix — for Eightfold the tenant domain is derived from the host (careers.micron.com → micron.com, vodafone.eightfold.ai → vodafone.com), and eightfold:careers.example.com/example-corp.com overrides that guess.
Workday needs the URL, not the name. Every other platform gives a company one guessable handle. A Workday board is addressed by a tenant and a site id — nvidia + NVIDIAExternalCareerSite, adobe + external_experienced — and the site id is arbitrary, so guessing it would only produce confident misses. Open the company's careers page and paste the address. All of these work, including a link to a single job (the board it belongs to is what gets watched):
https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSitehttps://adobe.wd5.myworkdayjobs.com/en-US/external_experiencedhttps://salesforce.wd12.myworkdayjobs.com/en-US/External_Career_Site/job/California---San-Francisco/Some-Job_JR123
What each platform's feed fills in. A dash means that feed does not publish the field, so the column is empty for that platform.
| Column | Greenhouse | Lever | Ashby | SmartRecruiters | Workable | Recruitee | Workday | Phenom | Eightfold |
|---|---|---|---|---|---|---|---|---|---|
title, url, job_id | yes | yes | yes | yes | yes | yes | yes | yes | yes |
department | yes | yes | yes | yes* | yes | yes | — | yes | yes |
team | —† | yes | yes | — | — | — | — | employer's choice‡ | — |
location | yes | yes | yes | yes | yes | yes | yes** | yes | yes |
remote | — | yes | yes | yes | yes | yes | yes | employer's choice‡ | yes |
employment_type | — | yes | yes | yes | yes | yes | with descriptions | yes | — |
published_at | yes | yes | yes | yes | yes | yes | yes*** | yes | yes |
updated_at | yes | — | — | — | — | yes | — | with descriptions | — |
description (opt-in) | yes | yes | yes | — | yes | yes | yes | yes | yes |
* SmartRecruiters leaves department to each employer and most large boards skip it; where it is empty the column carries the job function SmartRecruiters groups its own search by (Engineering, Sales). Both values come from the source.
** Workday's list feed says 3 Locations for a multi-office req; includeDescription resolves it to a real location.
*** Workday publishes relative wording (Posted 6 Days Ago), translated to a date and shipped alongside as posted_relative; the open-ended Posted 30+ Days Ago bucket becomes null. Details under Honest limits.
† Greenhouse carries a team only when an employer files a job under a second department, and none measured does: 0 of 2,181 jobs across six boards (stripe, databricks, figma, cloudflare, discord, airbnb). Expect this column to be empty for Greenhouse.
‡ Phenom leaves both to the employer. team is the feed's subCategory: filled on 73% of jobs.cvshealth.com's 19,036 postings and on none of careers.humana.com's 2,191 or careers.lilly.com's 620 (2026-09-07). remote is the feed's isRemote (Yes/No): on 100% of Humana's postings, absent from Lilly's and CVS's. Eightfold's remote comes from workLocationOption (onsite/hybrid/remote; only remote is true), set on 100% of the 464 Ericsson and 100 Micron postings measured; its list feed carries no employment type or edit time at all.
Not covered: any other applicant-tracking system (iCIMS, Taleo, SuccessFactors, BambooHR, Jobvite, Personio, Teamtailor and the rest), company careers pages built without a feed, and aggregators such as LinkedIn or Indeed. A URL on one of those gets a free not-found row (after the two Phenom/Eightfold probes described above come back empty-handed), never a guess. See Need another job board? below.
How much does it cost?
Pricing is pay-per-event: $0.0015 per job row delivered, and nothing else — no fee per run, no charge for status rows, no charge on a monitor run where nothing changed.
| What happens | You pay |
|---|---|
| 50 new jobs delivered | $0.075 |
| 500 new jobs delivered | $0.75 |
| 1,000 new jobs delivered | $1.50 |
| First run on 100 companies with 5,000 openings between them | $7.50, once |
| A scheduled monitor run that finds 40 new jobs | $0.06 |
A scheduled monitor run that finds nothing new (100 free no-new-jobs rows) | $0.00 |
Only rows you actually receive are billed: jobs your title filters removed, jobs you were already given on an earlier monitor run, and every status row are free. If you set a maximum cost for the run, delivery stops at that limit rather than handing you rows it cannot bill, you get a free charge-limit-reached row, and the held-back jobs are not marked as seen, so they arrive on your next run. On a run that is not billed per event, job_rows_charged in RUN_SUMMARY reads 0 beside pay_per_event: false.
How to use it
- List your companies. Bare names for Greenhouse, Lever, Ashby, Workable and Recruitee (and SmartRecruiters, once its host permits the feed again — see Honest limits); a careers-page URL for Workday, Phenom and Eightfold (or for any company, to skip auto-detection).
- Press Start for a one-off snapshot of every open job. Add
titleIncludes/titleExcludesto narrow it, andmaxJobsPerCompanyto cap a large board. - Turn on
monitorModeand schedule it. In Apify Console, open Schedules in the left menu, create a schedule, choose this actor, pick daily or hourly, and paste the same input. From the second run on, each run returns only new postings. - Get told about new jobs. On the actor's Integrations tab, connect the run's dataset to Slack, Gmail, Google Sheets, Zapier, Make or a webhook of your own; each scheduled run then pushes its new rows where you want them. The dataset is also available as JSON, CSV or Excel from the run page and through the Apify API.
Example input:
{"companies": ["stripe","https://jobs.lever.co/spotify","openai","https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite","https://careers.lilly.com/us/en/search-results","https://careers.micron.com/careers"],"monitorMode": true,"titleIncludes": ["engineer", "developer"],"titleExcludes": ["senior", "staff"]}
Output examples
A job row:
{"company": "stripe","platform": "greenhouse","job_id": "7532733","title": "Backend Engineer, Payments","department": "Engineering","team": null,"location": "Remote, US","remote": null,"employment_type": null,"url": "https://stripe.com/jobs/search?gh_jid=7532733","published_at": "2026-08-12T09:11:04-04:00","updated_at": "2026-08-20T11:02:55-04:00"}
Free status rows:
{"company": "bunq","status": "filtered-out","note": "13 open jobs found, 0 matched your title filters"}
{"company": "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite","status": "results-truncated","note": "the workday feed reports 2675 postings for this board but its public API served only 2000 — the rest are not available through it"}
Who uses a company jobs monitor
- Recruiters and sourcers — watch competitors' and clients' openings without refreshing careers pages; new roles land in Slack or a sheet each morning.
- Job seekers — your shortlist of companies, filtered to the titles you would actually apply to, checked on a schedule so you see a posting the day it appears.
- Sales and lead generation — a company opening engineering or sales roles is a company with budget. New job postings are a hiring signal you can act on the day they appear.
- Analysts and job boards — fresh listings straight from the source, with the board's size in every RUN_SUMMARY and any shortfall flagged beside it.
Honest limits
Everything below was measured against the live sources on 2026-09-03 (Phenom and Eightfold on 2026-09-07).
Paging: every feed is read until it ends, and you are told who stopped us. A feed ends with a short page, an empty page, or by starting to re-serve page 1 (Workday does that instead of ending, so rows are de-duplicated as they arrive and a wrap can never inflate a count).
- Workday — 20 postings per request; a larger page is refused outright with HTTP 400 on every tenant tried. Most tenants serve their whole board:
pwc.wd3hands over 4,385 postings across 220 requests. Some tenants clamp, and that is detected rather than assumed:nvidia.wd5serves 2,000, then re-serves page 1, and reportstotal: 2000— while the same response's facet counts add up to 2,675 on three independent facets. Before calling that a source ceiling, this actor asks the feed for the whole board in one page; nvidia refuses, which is what makes the claim true. That run gets a freeresults-truncatedrow naming both numbers. - SmartRecruiters (paging as measured before its host closed the feed by
robots.txton 2026-09-07; not read today) — states its own page size: ask for 1,000 and the payload answerslimit: 100with 100 rows, so 100 is what it is paged at. It statestotalFoundon every page and ends cleanly (BoschGroup: 4,826 postings over 49 requests, measured 2026-09-03). Served is compared againsttotalFound, so a board that ends short of its own advertised size is reported truncated. Since 2026-09-07 this feed is not read at all, becauseapi.smartrecruiters.com/robots.txtdisallows it for this actor — see A host's robots.txt is read before its feed is below; the paging description stands for the day the host permits it again. - Lever — honours whatever page size it is asked for (gopuff: the whole 765-posting board in a single request), so it is asked for one big enough to clear a board in one go and no page boundary is created for a row to fall through. A board larger than that still pages by
limit+skipuntil the feed ends. Lever publishes no total, so a board's size is knowable only by reading the feed to its end — which is whymaxJobsPerCompanylimits the rows you are delivered there without shortening the read. - Greenhouse — one response for the whole board, cross-checked against the feed's own
meta.total(stripe 612 = 612, databricks 862 = 862). A server-side cap would show up as a truncation instead of a quiet gap. - Phenom —
from+sizeagainst the site's/widgetsfeed, 500 postings a request (a bigger page is clamped to 500 silently), with the board'stotalHitson every page. Two things about it are unusual and both are handled rather than hidden. First, the feed has a hard window:from + sizemay not pass 9,999 — a request at 9,999 answers zero hits and a zero total, whatever the board holds. A board above that (jobs.cvshealth.com, 19,036 postings) is therefore read one category at a time, using the facet counts the same feed publishes, and a category itself above 9,999 one US state at a time; the union is de-duplicated by job id andjobs_foundstays the feed's owntotalHits. Measured: CVS read whole in 10 partitions over about 46 requests (45–46 across reads, since a partition's last page depends on the day's counts), 19,013 and 19,028 of 19,036 on two reads. Second, the feed's ordering is not stable between requests, so a paged walk can lose a row at a page seam: two consecutive 5-page walks of careers.humana.com's 2,191 postings returned 2,190 and 2,191 distinct ids, and an unfiltered walk of the CVS window returned 9,997 distinct of 9,999. That seam loss sits inside the same small churn tolerance a live board's own comings and goings already get, and is never reported as a source ceiling — but it does mean a monitor run can, rarely, deliver a posting one run later than it appeared. Never earlier, never twice. If no facet can split a bucket below 9,999, the rows past the window are genuinely unreachable through the feed, and that run gets the freeresults-truncatedrow naming both numbers. - Eightfold —
GET /api/pcsx/searchwithstart, newest first (sort_by=timestamp), and the board'scounton every page. The page is a server-fixed 10 rows however it is asked, so a board costs one request per 10 postings: jobs.ericsson.com's 464 postings are 47 requests, careers.micron.com's 2,772 are 278, and starbucks.eightfold.ai's 22,985 would be 2,299. Eightfold also rate-limits anything faster than about one request a second (HTTP 429, or a 401 withPlease try again later, measured after ~12 requests in 13 s), so this actor spaces its requests a second apart and, on a refusal, backs off and retries up to three times before reporting the page lost. At that pace a large Eightfold board takes minutes, which is what the per-board time budget below is for. - Ashby, Recruitee, Workable — one response each, with no paging parameters to use. Largest boards measured: Ashby openai 770, Recruitee conclusion 150, Workable exoticca 43. These feeds publish no total, but one response is the whole board, so what they served is what
jobs_foundreports. Where a board's size genuinely cannot be established — a read cut short by the safety guard, a lost page, or a feed re-serving page 1 with no total to check against —jobs_foundfalls back to the rows actually read, andresults_truncatedorpartialin the same RUN_SUMMARY entry, plus the free status row, says why that number is not the board's size.
Our own limits are labelled as ours. Two of them, both reported as truncation_reason: our-guard with guard saying which. A runaway guard stops any paged read at 50,000 postings per board, so that a loop against a third-party feed cannot run away; it sits well above the largest board ever measured (starbucks.eightfold.ai, 22,985) and has never been reached on a live board. And a per-board time budget of 20 minutes stops a read that is too slow to finish inside a run: Eightfold's 10-row pages at one request a second put Starbucks at about 38 minutes, longer than a run with several companies can spend on one of them, and a run that times out delivers nothing and explains nothing. In both cases the free results-truncated row says this actor stopped the read, states the board's own size from the feed, and names the input that raises the limit — sourceSafetyGuard (exact: the page that trips it is trimmed back to it) or boardTimeBudgetMinutes (raise the run timeout with it). A capped read (maxJobsPerCompany) on Eightfold stops as soon as the cap is filled, because the board's size is on the first page.
A host's robots.txt is read before its feed is — every host. The vendor API hosts the seven hosted platforms answer on, and the employer domains Phenom and Eightfold live on, alike: a host's robots.txt is its owner's word on what may be read there, so it is fetched once per host per run, before the first feed request, and applied to each feed path the way RFC 9309 says: the group naming this actor (DockhandCareerJobsMonitor, or the family name Dockhand) outranks User-agent: *, the longest matching rule wins, * is a wildcard, a trailing $ anchors the end, and a redirect is followed (bunq.recruitee.com sends its robots.txt to careers.bunq.com). On a tie the least restrictive rule wins, exactly as RFC 9309 §2.2.2 says and as Google's reference parser reads it: when an Allow and a Disallow of equal length both match a path, the path is open. That is the shape a CDN produces when it prepends a managed User-agent: * / Allow: / above the host's own User-agent: * / Disallow: / (measured 2026-09-07). A host whose only rule is Disallow: /, with nothing to tie against, is closed and never asked — api.smartrecruiters.com is exactly that. A longer Allow still wins over a shorter Disallow, which is what keeps every Eightfold site readable (below). A path a Disallow covers is not requested — that company gets the free not-found row and RUN_SUMMARY says robots.txt disallows <path> on <host>. A robots.txt that answers 404 (or any 4xx) is "no rules" and the feed is asked; one that cannot be read at all — a network error, a timeout, a 5xx — is not permission, so nothing on that host is requested that run, and the reason says the rules could not be read rather than pretending the host is on no platform. Measured on 2026-09-07 with this actor's own identity, against the exact paths it requests: boards-api.greenhouse.io (Disallow: /embed/ only), api.lever.co (Allow: /, and Crawl-delay: 1, which is met by the fixed one-second spacing below — the Crawl-delay directive itself is not parsed), apply.workable.com (an empty Disallow), *.recruitee.com (Disallow: /v/ only) and *.myworkdayjobs.com (per-site rules that do not mention the /wday/cxs/ feed paths) all permit their feeds; api.ashbyhq.com answers HTTP 401 to /robots.txt, which is "no rules". api.smartrecruiters.com does not permit it: its robots.txt is User-agent: * / Disallow: /, with only LinkedInBot allowed /v1/companies/, so every SmartRecruiters board — by name or by URL — gets the free not-found row with robots.txt disallows /v1/companies/<Company>/postings on api.smartrecruiters.com and no feed request is made. No other path to that data is tried; the feed comes back the day the host's rules permit it. On the employer domains: careers.lilly.com, jobs.cvshealth.com and careers.humana.com (Phenom) disallow */apply, */px-widgets and their tracking paths and say nothing about /widgets; careers.micron.com, jobs.ericsson.com, paypal.eightfold.ai, starbucks.eightfold.ai and vodafone.eightfold.ai (Eightfold) are Disallow: / with Allow: /api/pcsx — which is exactly why the longest rule has to win.
About one request a second to any host, and no request is asked twice unless it went unanswered. Every request to one host waits about a second after the previous one to that host — vendor API hosts included, and Workday's per-posting description pages too (they used to be fetched six at a time). Hosts do not wait on each other, so a run over six companies on six hosts is not slowed by it; one large board is: a 220-request Workday board takes about four minutes without descriptions, a 49-page SmartRecruiters board would take about 50 seconds. A request that got no answer — a DNS or connection failure, a reset, a timeout, which the runtime reports only as "fetch failed" — is asked again, up to three attempts with a short back-off and a 60-second budget per request, and so is an answered "not now" (HTTP 408, 425, 429, 500, 502, 503, 504) on a request that is safe to repeat; when the host still will not answer, RUN_SUMMARY names the host, the attempt count and the underlying error. An answered "no" (a 404, a 400, a 403) is never asked twice: it is the source's answer and it is reported as it came. A bot-verification challenge (Cloudflare's managed challenge, AWS WAF's interstitial, and their kin) is told apart from both: it is a fact about the network this run was made from, not about the board — the same feeds commonly answer another network normally in the same hour — so it is named as such in the free row and in RUN_SUMMARY (reason: walled, sources_walled), no workaround is attempted, and it never fails the run on its own. The run fails only when every company it was given produced no board and at least one of them was a genuine outage — a host that never answered, or an error answer; a run whose companies were all challenged, all refused by robots.txt, or all on no platform, exits normally with every row explained, so that a green run is never an empty one nobody could read, and a red run is never a network's mood.
Reported counts mean what the source holds. maxJobsPerCompany caps what you are delivered and charged for. It shortens the read only where that cannot change which rows you end up with, and jobs_found reports the board's real size — a run will never tell you a company has 5 openings because you asked for 5. The one time jobs_found is not the board's size is a read cut short or missing a page on a feed that states no total, and the same entry says so.
A lost page is reported, not absorbed. A page request that got no answer, or a "not now", is asked again as described above; if the page is still lost, that company's rows are delivered as far as they were read, with a free partial row and partial_results: true in RUN_SUMMARY (a board whose very first request fails gets the free not-found row instead — see the FAQ). A shrunken count never gets to pose as a board's real size.
Monitor mode remembers exactly two things: what it delivered to you, and what your own title filters excluded. Both have had their outcome. Rows held back for any other reason — the per-company cap, the run's charge limit — are not remembered and arrive on your next run. Widening titleIncludes or dropping a titleExcludes word applies to jobs posted from then on, not to jobs already excluded under the old filter; to re-sweep a board's back catalogue, change the company list or clear the state store.
An interrupted run never bills you twice. State is saved after every company delivered, not once at the end, so a run cut short — platform timeout, migration, an aborted run — keeps what it already handed you and the next run carries on from there. State is saved before the charge, so an interruption at exactly the wrong moment leaves you with rows you were not billed for, never a bill for rows delivered twice.
If billing itself fails, the run stops. Delivery halts there, the companies after it get a free charge-limit-reached row, and the rows already written are not marked as seen, so your next run returns them.
published_at precision differs by platform. Greenhouse, Lever, Ashby, SmartRecruiters and Recruitee publish a full ISO timestamp; Workable and Workday publish a date only, and a bare date is never padded out to a fake midnight. Two feeds publish a date dressed as a timestamp, and both ship as they came, normalised to UTC, so you know the time part is the source's and not ours: Phenom's postedDate is a midnight timestamp of its own (2026-08-10T00:00:00.000+0000), and Eightfold's postedTs is epoch seconds at midnight UTC of the day the careers page shows as posted (1788739200 → 2026-09-07T00:00:00.000Z; 10 of 10 postings sampled on careers.micron.com, 2026-09-07) — date precision, not a time of day. Sorting works across all of them.
Workday dates are relative at the source. Its board feed publishes Posted Today, Posted 6 Days Ago — not timestamps. The exact buckets are translated into a published_at date (checked against Workday's own internal posting date: 14 of 14 exact) and the source's wording always ships alongside as posted_relative. Postings in the open-ended Posted 30+ Days Ago bucket get published_at: null, because that bucket is not a date. Expect roughly 50–100% of a tenant's Workday rows to carry a date, depending on how fast it posts.
Workday locations and departments. When a req is open in several offices the board feed literally says 3 Locations, so that is what the location column says. The feed carries no department, team or employment type at all. Turn on includeDescription and each posting's own page is fetched, which fills in the resolved location, the employment type and the real posting date — at one extra request per posting, one a second on the tenant's host, inside the same per-board time budget as Phenom and Eightfold: a 1,000-posting Workday board is about 17 minutes of descriptions, and what does not fit the budget is counted in a free partial row, never left blank silently. Set maxJobsPerCompany or raise boardTimeBudgetMinutes for a large Workday board with descriptions on.
Descriptions are plain text. includeDescription adds the posting's description with HTML tags stripped; SmartRecruiters' board feed carries no description, so that column stays empty there. On Workday, Phenom and Eightfold the description is a separate request per posting (the job page, jobDetail, position_details), made one a second on the board's host, so a 620-posting board takes ten minutes of descriptions; Phenom's detail also fills updated_at and Eightfold's confirms the public URL. If the per-board time budget runs out before every description is fetched, the rows still ship and a free partial row says exactly how many lack one — a blank description is never passed off as the source having none. Set maxJobsPerCompany or raise boardTimeBudgetMinutes for a large board with descriptions on.
Experience-level filtering is not offered, because these feeds do not publish it reliably. Title keywords are the honest proxy.
FAQ
Is this legal? It reads only the public, logged-out JSON job feeds that Greenhouse, Lever, Ashby, Workable, Recruitee, Workday, Phenom and Eightfold publish so their customers' careers pages can list openings — the same data anyone sees by visiting the page. No login, no proxies, no browser automation, no attempt to get around a block or a challenge, every request identifies itself as this actor, and no host is asked more than about once a second. Every host's robots.txt is read first and obeyed — the vendors' API hosts and employers' own domains alike; see A host's robots.txt is read before its feed is under Honest limits. Measured 2026-09-07: six of the seven vendor hosts permit their feeds and api.smartrecruiters.com does not, so SmartRecruiters boards are not read until it does; Phenom sites disallow their apply and tracking paths (*/apply, */px-widgets, …), not the /widgets jobs feed; Eightfold sites are Disallow: / with an explicit Allow: /api/pcsx, which is the jobs feed. Job postings are company information, not personal data; no candidate or recruiter details are collected.
How fresh is the data? Each run reads the live feed at that moment, so freshness is your schedule: an hourly schedule catches a new posting within the hour. published_at is the posting date the source states. In monitor mode a job counts as new the first time it appears on a board you watch, whatever its posting date — so the first monitor run returns the whole board, and later runs return only what was not there before.
What happens when a source blocks or fails? A request that got no answer is asked again (three attempts, short back-off); a request the source answered "no" to is not. If a board's first request still fails — the host never answered, an HTTP error, a body that is not the feed — that company gets a free not-found row whose note says which of those it was, and RUN_SUMMARY carries the exact answer (detail) and a one-word reason; nothing is marked as seen, so the next run picks that company back up. If a page fails mid-board, you get the rows that were read plus a free partial row. A bot-verification challenge is reported as exactly that — reason: walled, with the note saying it is about this run's network rather than the board — and does not fail the run; a genuine outage of every company in the run does. There is no retry through proxies or a browser: a source that refuses is reported, not fought.
Why does Workday need a URL when the others take a name? Because a Workday board is keyed by a tenant and an arbitrary site id, and guessing the site id would only produce confident misses. Paste the careers-page address; see Which job boards it reads.
Why do Phenom and Eightfold need a URL too? Because their sites live on the employer's own domain — careers.lilly.com, jobs.cvshealth.com, careers.micron.com — and nothing about a company name says which host that is, or whether it is a Phenom or an Eightfold site at all. Paste any page of the careers site and this actor asks the host which of the two it is; if you already know, the phenom: and eightfold: prefixes skip the question.
How do I reset monitor mode? Delete the key-value store named career-jobs-monitor-state in your Apify Storage, or change the company list (state is kept per list).
Why pay per row instead of buying a jobs database? Job databases are built for searching everything, so watching a fixed company list means re-buying the same unchanged rows every day plus a fee per run. Here you pay only for the job rows you receive, and a monitor day with nothing new costs nothing.
Will a huge board time out? The default run timeout is one hour. Every host is asked about once a second, so a board costs about as many seconds as it costs requests: the largest Workday board measured (4,385 postings over 220 requests) is about four minutes without descriptions; Phenom's largest (CVS, 19,036) is about 46 requests (45–46 across reads) and under a minute. Eightfold is the slow one — 10 postings a request — so its boards above roughly 12,000 postings hit the 20-minute per-board budget and get the free results-truncated row rather than a timed-out run; raise boardTimeBudgetMinutes and the run timeout together to read one whole, or use maxJobsPerCompany. With includeDescription on, budget one extra request per posting, one a second, on Workday, Phenom and Eightfold — the same per-board budget applies, and descriptions it does not reach are counted in a free partial row.
Need another job board?
If your target companies hire through an applicant-tracking system not listed here, open an issue on this actor and name it. You will get an answer within a day, and if the platform publishes a public feed, coverage typically follows within days. More Dockhand tools: https://apify.com/dockhand
Related
Watching law firms? Law Firm Jobs Monitor — Legal Job Alerts from BigLaw Portals is the sibling actor: the same only-new monitor design, reading the hiring portals law firms actually use (viRecruit/viGlobal, cvMail, AllHires, WizeHire) instead of job aggregators. Both are on our publisher page.
About Dockhand
Dockhand builds data tools that count every row against the source and say exactly what is missing. Questions or a platform request: open an issue on this actor.