ATS Job Change Feed API - Greenhouse + 7 More
Pricing
from $1.60 / 1,000 job or change records
ATS Job Change Feed API - Greenhouse + 7 More
ATS job change feed and career page API for Greenhouse, Workday, Lever, Ashby, Workable, Personio, Recruitee and Teamtailor. Monitor normalized job postings and receive CREATED, UPDATED and CLOSED events while failed sources retain their last healthy state.
Pricing
from $1.60 / 1,000 job or change records
Rating
0.0
(0)
Developer
Kamer Ozkan
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
5 days ago
Last modified
Categories
Share
ATS job change feed API and career page monitor
Monitor company hiring changes across eight ATS platforms - directly from career pages, with reliable closed-job events and no false closures when a source fails.
This Actor is a source-direct ATS job change feed API, career page monitor, and
job scraper API for Greenhouse, Workday, Lever, Ashby, Workable, Personio, Recruitee,
and Teamtailor. Give it company websites or ATS board URLs and receive one normalized schema
with stable job IDs plus deterministic CREATED, UPDATED, and CLOSED events.
Use it to track competitor hiring, trigger B2B sales workflows, build job alerts, or maintain
a reliable recruiting-analytics feed. In scheduled changes mode, unchanged jobs produce no
result records, so frequent monitoring does not repeatedly bill the same snapshot.
Run the Actor | Open the API page | Download example inputs and schema
Unofficial integration. This Actor is independently developed and is not affiliated with, sponsored by, or endorsed by Greenhouse, Lever, Ashby, Workable, Recruitee, Personio, Teamtailor, or Workday. Product names are used only to identify compatible public career-board formats. It reads public company job postings only - never candidates, applications, resumes, or private ATS data.
Pick the workflow that matches your goal
| Goal | Start with this live Example | Output | Recommended run pattern |
|---|---|---|---|
| Trigger CRM or account-research workflows from hiring changes | Track competitor hiring changes | Only CREATED, UPDATED, and CLOSED events after the baseline | Daily or every 10 minutes |
| Build a normalized job-alert or recruiting-analytics feed | Build a multi-ATS job change feed | Stable job IDs, provenance, and lifecycle events | Scheduled |
| Export current jobs from company career pages | Scrape company career pages | Current normalized job rows | On demand |
| Analyze DACH skills, languages, locations, and salary signals | Track DACH job market signals | Current jobs plus DACH enrichment | Daily or weekly |
For the fastest production setup, open an Example, replace its company URL or board URL,
and run one capped baseline. Check RUN_SUMMARY, then save the input as a task with the same
watchlistId and connect the successful-run webhook to your CRM, database, or alerting flow.
Start with one or two companies before expanding the watchlist.
Buyer workflows for agencies, RPOs, and market intelligence
The Actor is useful when the buyer needs a repeatable company watchlist rather than a
one-time job export. Keep one stable watchlistId per client, portfolio, or research cohort
so its baseline and later change events stay isolated from other accounts.
| Buyer | Operational workflow | Evidence to route downstream | Boundary |
|---|---|---|---|
| Recruitment agency or executive search team | Monitor target employers and existing client career pages for role openings and closures | CREATED, UPDATED, and CLOSED events with company, role, location, source URL, and timestamps | A hiring change is a prospecting signal, not proof that an employer will buy recruitment services |
| RPO or MSP team | Reconcile public client requisitions across supported ATS career pages and flag lifecycle changes for review | Stable job IDs, explicit change type, and RUN_SUMMARY board outcomes | This does not read private requisitions, candidates, applications, or the client's ATS account |
| Recruitment market intelligence team | Build a source-direct time series by role, location, skill, language, salary signal, or ATS | Normalized records, provenance, duplicate markers, and change timestamps | Enrichment is deterministic heuristic metadata and needs review before regulated or statistical use |
| Private equity, venture capital, or portfolio operations team | Monitor public portfolio-company and target-company hiring activity in separate cohorts | Current jobs plus new, edited, and closed role events | Public hiring activity is one operating signal, not verified headcount, financial performance, or investment advice |
This is therefore an RPO job monitoring API, recruitment market intelligence feed, and portfolio company hiring monitor built on public career pages. It does not replace an ATS, CRM, investment data room, or human review. Use the change feed as an input to those systems.
Features and key differences
Most job scrapers return a fresh snapshot and leave change tracking, deduplication, and failure handling to you. This Actor is designed as an operational job change feed.
| Typical snapshot scraper | ATS Job Change Feed API |
|---|---|
| Returns every open job again | Emits only real changes in changes mode |
| Downstream system must infer closures | Emits explicit CLOSED events |
| Empty response can look like mass closure | Failed or malformed sources retain prior state |
| One ATS-specific schema | One schema across eight ATS formats |
| Requires known ATS URLs | Can discover boards from company websites |
| Generic locations and text | DACH locations, KldB, languages, skills, and salary signals |
| Duplicate suppression is opaque | Cross-ATS duplicates remain visible and auditable |
What the Actor does
Job postings often appear on the employer's own ATS career board before they reach job aggregators. The Actor connects to those source endpoints and:
- reads public career-board JSON, XML, and RSS without a browser, login, or candidate account;
- discovers supported ATS boards from public company and careers pages;
- normalizes eight ATS formats into one stable schema;
- assigns deterministic IDs based on the ATS board and source job ID;
- assigns stable company identities and marks likely cross-ATS duplicates;
- records
firstSeenAt,lastSeenAt, published, and updated timestamps; - detects new, edited, and closed jobs across scheduled runs;
- merges multi-location variants instead of returning duplicates;
- recognizes DACH job types such as
WORKING_STUDENT,APPRENTICESHIP, andMINIJOB; - enriches DACH locations, KldB groups, language requirements, skills, and salary ranges;
- keeps a failed board's previous snapshot, preventing false mass closures;
- can bootstrap a versioned 10,000-board candidate catalog and process it in deterministic lifecycle-managed chunks.
Quick start: monitor jobs in 30 seconds
Paste one or more company career pages. The Actor discovers supported ATS boards and creates the first baseline:
{"boards": [],"companyUrls": ["https://linear.app/careers"],"outputMode": "changes","watchlistId": "competitor-hiring-watchlist","emitInitialSnapshotChanges": true}
Save the input as a task and schedule it every ten minutes. Later runs return only new, updated, and closed jobs. If a board fails, its last successful snapshot is preserved and no synthetic closures are emitted.
API integration
The same input works in Apify Console, the REST API, JavaScript, Python, Make, n8n, and the Apify MCP server. Keep API tokens in environment variables or a secret manager and never commit them to source control.
Run synchronously with curl
This endpoint waits for completion and returns the default dataset items directly:
curl --fail-with-body \--request POST \"https://api.apify.com/v2/acts/kamerozkan~ats-job-change-feed/run-sync-get-dataset-items" \--header "Authorization: Bearer YOUR_APIFY_TOKEN" \--header "Content-Type: application/json" \--data '{"boards": [{"url": "https://job-boards.greenhouse.io/greenhouse","company": "Greenhouse"}],"outputMode": "jobs","watchlistId": "api-quick-start"}'
JavaScript client
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('kamerozkan/ats-job-change-feed').call({companyUrls: ['https://linear.app/careers'],outputMode: 'changes',watchlistId: 'linear-watchlist',});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items);
Python client
import osfrom apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])run = client.actor("kamerozkan/ats-job-change-feed").call(run_input={"companyUrls": ["https://linear.app/careers"],"outputMode": "changes","watchlistId": "linear-watchlist",})items = client.dataset(run["defaultDatasetId"]).list_items().itemsprint(items)
For asynchronous runs, read defaultDatasetId from the completed run and request
https://api.apify.com/v2/datasets/{defaultDatasetId}/items?clean=true. Operational reports
are stored under RUN_SUMMARY, DISCOVERY_REPORT, FRESHNESS_REPORT, QUALITY_REPORT,
CATALOG_REPORT, and CATALOG_TASK_PLAN in the run's default key-value store.
Supported career boards
| ATS | Accepted board URL |
|---|---|
| Greenhouse | https://job-boards.greenhouse.io/{company} |
| Greenhouse legacy | https://boards.greenhouse.io/{company} |
| Lever | https://jobs.lever.co/{company} |
| Lever EU | https://jobs.eu.lever.co/{company} |
| Ashby | https://jobs.ashbyhq.com/{company} |
| Workable | https://apply.workable.com/{company} |
| Recruitee | https://{company}.recruitee.com |
| Personio | https://{company}.jobs.personio.de |
| Teamtailor | https://{company}.teamtailor.com |
| Workday | https://{tenant}.wd{cluster}.myworkdayjobs.com/{career-site} |
You can also provide an explicit ats and slug. A single Workable job URL does not contain
the company's account slug and cannot be used as a board URL. Workday requires a board URL
because both its tenant and career-site name are needed.
Teamtailor is read from its public jobs RSS feed and requires no API token. Workday search
results are fetched with consistency-checked pagination. When includeDescriptions is
enabled, the Actor also requests every Workday job-detail record; disabling descriptions
reduces request count substantially on large Workday boards.
Company-domain discovery
When the ATS URL is unknown, pass a company home page or careers page in companyUrls.
The Actor scans a small, bounded set of public pages and follows career-related links. Every
discovered board and job keeps its origin URL and discovery-page provenance.
{"boards": [],"companyUrls": ["https://linear.app/careers"],"outputMode": "changes","watchlistId": "linear-watchlist"}
The discovery result is written to the default key-value store as DISCOVERY_REPORT. A URL
that cannot be resolved does not stop other companies by default. Set
failOnDiscoveryError to true when complete discovery is required.
Monitor explicit ATS board URLs
Use changes mode for monitoring. The first run emits every current job as CREATED.
Later runs return only jobs that appeared, changed, or closed.
{"boards": [{"url": "https://job-boards.greenhouse.io/greenhouse","company": "Greenhouse"},{"url": "https://jobs.lever.co/ae-2","company": "AE"}],"outputMode": "changes","watchlistId": "competitor-watchlist","includeDescriptions": true,"includeCompensation": true,"emitInitialSnapshotChanges": true}
For continuous monitoring, save this input as an Apify task and run it on a schedule. Keep
the same watchlistId for that watchlist and do not overlap runs using the same ID.
Input configuration
| Field | Type | Default | Description |
|---|---|---|---|
boards | array | Greenhouse example board | Board URL strings or { url, ats, slug, company } objects |
companyUrls | string[] | [] | Company home or careers URLs to scan for supported ATS boards |
catalog | object | disabled | Bootstrap/process the bundled 10K candidate catalog and configure chunk/lifecycle rules |
outputMode | string | changes | changes, jobs, or both |
watchlistId | string | default | User-chosen snapshot namespace; use a unique value per independent watchlist |
includeDescriptions | boolean | true | Return descriptions and detect description edits |
includeCompensation | boolean | true | Normalize salary data when exposed by the ATS |
emitInitialSnapshotChanges | boolean | true | Emit CREATED for every job on the first run |
maxConcurrency | integer | 10 | Boards fetched in parallel, from 1 to 50 |
requestTimeoutSecs | integer | 30 | Timeout for each request attempt |
maxRequestRetries | integer | 2 | Retries for rate limits, server errors, and network failures |
failOnBoardError | boolean | false | Fail the run instead of returning a partial result |
discoveryMaxPages | integer | 5 | Maximum public pages scanned for each company URL |
failOnDiscoveryError | boolean | false | Fail if any company URL cannot be resolved |
freshnessSlaMinutes | integer | 10 | Source-to-detection SLA target used in freshness reports |
At least one item is required across boards and companyUrls, unless catalog.enabled
is true.
Output modes
changes- recommended for scheduled monitoring. Full jobs on the first run, then only changes.jobs- every currently open job on every run.both- open jobs followed by change records. This can produce two billable records for the same job on the first run.
Output examples
The records below follow one real job on a real Recruitee career board through its
lifecycle. The normalized job record and the CREATED event are unedited output from
live scheduled runs on 2026-07-28. The UPDATED and CLOSED events were produced by
the production change engine replaying the same board with one salary edit and one
closure, because neither had yet occurred naturally on this board when this page was
written; every field is real engine output. Long descriptions are shortened here.
1. Normalized job record (jobs mode)
One remote role posted across four locations, merged into a single record instead of
four duplicates. The Cologne location activates DACH enrichment: structured
locationDetails, dachRegion, a conservative KldB occupational group, and extracted
language requirements. Fields the source does not publish stay null.
{"recordType": "job","status": "OPEN","id": "fb650323dd54bfe995e5ed74be41edf3","jobKey": "recruitee:jobs:2659009","ats": "recruitee","boardSlug": "jobs","sourceJobId": "2659009","company": "Tellent","title": "Senior Backend Engineer (Node.js)","locations": ["Remote job","Amsterdam, Noord-Holland, Netherlands","Cologne, Nordrhein-Westfalen, Germany","Poznań, Wielkopolskie, Poland"],"locationDetails": [{"raw": "Remote job","city": null,"region": null,"country": null,"countryCode": null,"isDach": false,"isRemote": true},{"raw": "Amsterdam, Noord-Holland, Netherlands","city": null,"region": null,"country": null,"countryCode": null,"isDach": false,"isRemote": false},{"raw": "Cologne, Nordrhein-Westfalen, Germany","city": "Cologne","region": "North Rhine-Westphalia","country": "Germany","countryCode": "DE","isDach": true,"isRemote": false},{"raw": "Poznań, Wielkopolskie, Poland","city": null,"region": null,"country": null,"countryCode": null,"isDach": false,"isRemote": false}],"dachRegion": "DE","primaryLocation": "Remote job","department": null,"team": null,"employmentType": "FULL_TIME","employmentTypeRaw": "fulltime_permanent","workplaceType": "REMOTE","workplaceTypeRaw": "remote","salary": null,"descriptionText": "Note: The listed salary range is based on the salary range for the Netherlands. Salary ranges may vary by loca ...","descriptionHtml": "(HTML description, shortened here)","publishedAt": "2026-07-23T11:22:12.000Z","updatedAt": "2026-07-23T11:22:12.000Z","sourceUrl": "https://careers.tellent.com/o/senior-backend-engineer-nodejs","applyUrl": "https://careers.tellent.com/o/senior-backend-engineer-nodejs/c/new","boardUrl": "https://jobs.recruitee.com","sourceApiUrl": "https://jobs.recruitee.com/api/offers","boardOrigin": "explicit","discoveredFrom": null,"companyId": "e4a478fc861a91b37c616f24","companyDomain": null,"canonicalJobId": "4810f6bb90b4cd1e72be89863716be64","duplicateGroupId": null,"isDuplicate": false,"duplicateOf": null,"kldb": {"code": "43","title": "Informatik-, Informations- und Kommunikationstechnologieberufe","level": 2,"version": "KldB 2010 – Fassung 2020","confidence": 0.9,"method": "title-keyword-v1"},"languageRequirements": [{"language": "English","level": "C1","evidence": "rimary area of expertise. Strong communication skills and fluency in English. What we offer Hybrid or remote working setup across Amsterdam, the Cologne area, and Po"}],"skills": ["Node.js","React"],"firstSeenAt": "2026-07-28T12:27:33.329Z","lastSeenAt": "2026-07-28T12:27:33.329Z","scrapedAt": "2026-07-28T12:27:33.329Z"}
2. CREATED and UPDATED change events (changes mode)
Every change event carries convenient top-level fields for tables plus the full
normalized job in job. The CREATED event below is the real first observation of the
job above.
{"recordType": "change","eventType": "CREATED","eventId": "fa7c2b45bb559a4feaa57995a7914972","detectedAt": "2026-07-28T12:27:33.329Z","jobKey": "recruitee:jobs:2659009","ats": "recruitee","boardSlug": "jobs","company": "Tellent","title": "Senior Backend Engineer (Node.js)","primaryLocation": "Remote job","sourceUrl": "https://careers.tellent.com/o/senior-backend-engineer-nodejs","boardOrigin": "explicit","discoveredFrom": null,"companyId": "e4a478fc861a91b37c616f24","canonicalJobId": "4810f6bb90b4cd1e72be89863716be64","duplicateGroupId": null,"isDuplicate": false,"duplicateOf": null,"changedFields": [],"job": "(full normalized job record embedded, identical to the record above)"}
When the employer later publishes a salary range, the fingerprint diff emits exactly one
UPDATED event, and changedFields names precisely what changed:
{"recordType": "change","eventType": "UPDATED","eventId": "18f9d2662c7a695f4995e59fd2004de1","detectedAt": "2026-07-28T13:27:31.212Z","jobKey": "recruitee:jobs:2659009","ats": "recruitee","boardSlug": "jobs","company": "Tellent","title": "Senior Backend Engineer (Node.js)","primaryLocation": "Remote job","sourceUrl": "https://careers.tellent.com/o/senior-backend-engineer-nodejs","boardOrigin": "explicit","discoveredFrom": null,"companyId": "e4a478fc861a91b37c616f24","canonicalJobId": "4810f6bb90b4cd1e72be89863716be64","duplicateGroupId": null,"isDuplicate": false,"duplicateOf": null,"changedFields": ["salary","updatedAt"],"job": {"status": "OPEN","title": "Senior Backend Engineer (Node.js)","salary": {"min": 65000,"max": 80000,"currency": "EUR","interval": "year","summary": "EUR 65,000 to 80,000 per year","source": "structured","confidence": 0.95},"firstSeenAt": "2026-07-28T12:27:33.329Z","lastSeenAt": "2026-07-28T13:27:31.212Z","note": "remaining job fields unchanged, shortened here"}}
3. CLOSED: what the feed guarantees when a job disappears
- Explicit closure. The job's final known state is delivered with
status: "CLOSED"and its completefirstSeenAt/lastSeenAthistory, so downstream systems never have to infer closures from absence. - No stale content. Stored snapshots deliberately drop descriptions, so a
CLOSEDrecord reports them asnullinstead of replaying outdated text. - No false mass closures. A failed, throttled, or malformed board keeps its previous
snapshot and emits no
CLOSEDevents at all. - No noise. The unchanged third job on this board produced no record in the same run, and an immediate repeat run on an unchanged board writes zero rows and bills zero events (measured: a 22-role board billed 22 events on first observation, 0 on the repeat).
{"recordType": "change","eventType": "CLOSED","eventId": "68f701d5a7f1d6fdfe428090d3c77b9e","detectedAt": "2026-07-28T13:27:31.212Z","jobKey": "recruitee:jobs:2650251","ats": "recruitee","boardSlug": "jobs","company": "Tellent","title": "Team Lead Customer Support - DACH","primaryLocation": "Remote job","sourceUrl": "https://careers.tellent.com/o/team-lead-customer-support-dach","boardOrigin": "explicit","discoveredFrom": null,"companyId": "e4a478fc861a91b37c616f24","canonicalJobId": "a2862a89b543c4ad7f2bb7576fc9b217","duplicateGroupId": null,"isDuplicate": false,"duplicateOf": null,"changedFields": ["status"],"job": {"recordType": "job","status": "CLOSED","id": "cfcce6be6280bf1e233895acbbcb511c","jobKey": "recruitee:jobs:2650251","ats": "recruitee","boardSlug": "jobs","sourceJobId": "2650251","company": "Tellent","title": "Team Lead Customer Support - DACH","locations": ["Remote job","Poznań, Wielkopolskie, Poland"],"locationDetails": [{"raw": "Remote job","city": null,"region": null,"country": null,"countryCode": null,"isDach": false,"isRemote": true},{"raw": "Poznań, Wielkopolskie, Poland","city": null,"region": null,"country": null,"countryCode": null,"isDach": false,"isRemote": false}],"dachRegion": null,"primaryLocation": "Remote job","department": null,"team": null,"employmentType": "FULL_TIME","employmentTypeRaw": "fulltime_fixed_term","workplaceType": "REMOTE","workplaceTypeRaw": "remote","salary": null,"descriptionText": null,"descriptionHtml": null,"publishedAt": "2026-07-14T07:24:04.000Z","updatedAt": "2026-07-14T12:48:50.000Z","sourceUrl": "https://careers.tellent.com/o/team-lead-customer-support-dach","applyUrl": "https://careers.tellent.com/o/team-lead-customer-support-dach/c/new","boardUrl": "https://jobs.recruitee.com","sourceApiUrl": "https://jobs.recruitee.com/api/offers","boardOrigin": "explicit","discoveredFrom": null,"companyId": "e4a478fc861a91b37c616f24","companyDomain": null,"canonicalJobId": "a2862a89b543c4ad7f2bb7576fc9b217","duplicateGroupId": null,"isDuplicate": false,"duplicateOf": null,"kldb": null,"languageRequirements": [],"skills": [],"firstSeenAt": "2026-07-28T12:27:33.329Z","lastSeenAt": "2026-07-28T12:27:33.329Z","scrapedAt": "2026-07-28T12:27:33.329Z"}}
Company identity and cross-ATS duplicates
Jobs receive a deterministic companyId. Discovered boards use the input company domain;
explicit boards fall back to a normalized company name. Across successful boards in the same
run, likely duplicate jobs are clustered using normalized title, compatible location and
employment type, description similarity, and publication timing.
The Actor does not silently delete duplicate records. It marks non-canonical members with
isDuplicate=true, sets duplicateOf to the richer canonical source record, and gives every
cluster member the same canonicalJobId and duplicateGroupId. Filter isDuplicate=false
when a single-record feed is desired. Keeping the source records makes migration periods and
partial board failures auditable.
DACH enrichment
Every job includes structured locationDetails and a dachRegion (DE, AT, CH, or
MULTI) when a supported country or major city is recognized. Titles are mapped
conservatively to the official two-digit occupational main groups from the
KldB 2010 - revised 2020.
The result includes its level, version, confidence, and the title-keyword-v1 method so it
cannot be confused with an official individual-case coding decision.
languageRequirements extracts German, English, French, Italian, Dutch, and Spanish
requirements with explicit CEFR levels or a normalized qualitative level. skills contains
canonical matches for common engineering, data, cloud, enterprise, and go-to-market tools.
When the ATS has no structured compensation, plausible EUR or CHF ranges can be recovered
from salary-context sentences in the description. Every salary reports source and
confidence.
Change detection and reliability
Each successfully fetched board receives its own snapshot in the persistent
ats-job-change-feed-state key-value store. Business fields are fingerprinted individually,
so UPDATED records identify exactly which normalized fields changed.
Safety behavior:
- HTTP rate limits, server errors, and network failures are retried.
- A malformed HTTP 200 response is rejected instead of interpreted as an empty board.
- Failed boards keep their previous snapshot and emit no
CLOSEDevents. - Location ordering is canonicalized for comparison to avoid false updates.
- Snapshot state advances only after that board's complete output is stored.
- If the run's maximum charge cannot cover a whole board, that board is deferred atomically. Increase the maximum charge and rerun; its prior snapshot remains intact.
The RUN_SUMMARY record reports discovery, successful, failed, and budget-deferred board
counts. DISCOVERY_REPORT lists the pages scanned, resolved boards, and per-company errors.
Ten-minute monitoring and SLA
For a ten-minute feed, save the Actor input as an Apify task and create an exclusive schedule
with cron expression */10 * * * *. Exclusive scheduling prevents a new run from starting
while the prior one is still active. Keep one stable watchlistId and use the same timezone for
operational dashboards; UTC is the simplest default.
Every run writes FRESHNESS_REPORT to the default key-value store. It includes per-board
fetch duration, source timestamp age, failures, and p50/p95/max detection latency. Baseline
CREATED events are excluded. Later CREATED events use publishedAt, UPDATED events use
updatedAt, and events without a compatible source timestamp are not presented as measured
SLA samples. complianceRate=null therefore means “no measurable events,” not 0% compliance.
Use the Greenhouse monitoring example as a starting point.
Integrations: Make, n8n, and webhooks
Add a webhook for the ACTOR.RUN.SUCCEEDED event (Actor page > Integrations). In
scheduled changes mode a run's dataset contains only the new CREATED, UPDATED,
and CLOSED records, so your endpoint receives the run notice and fetches exactly the
delta in one call:
GET https://api.apify.com/v2/datasets/{resource.defaultDatasetId}/items?format=json&clean=1
In Make and n8n you can skip the webhook entirely: both have a native Apify integration with a run-finished trigger and a get-dataset-items action, so a scheduled monitor-then-notify flow takes two modules.
Pilot quality benchmark
Every run also writes QUALITY_REPORT. It measures board and discovery success rates, unique
companies toward the 500-company pilot target, field completeness, ATS distribution,
duplicate groups, DACH coverage, enrichment coverage, and errors grouped by ATS. This keeps
the pilot exit criteria tied to observed data instead of a successful process exit alone.
The bundled pilot was generated from versioned public board candidates and validated on 26 July 2026. It selected 500 live boards across all eight adapters and normalized 15,923 open jobs. A rate-limited immediate repeat emitted zero observed changes or synthetic closures on successful sources. The same fixed 500-board list was then rechecked with per-provider concurrency limits: 500/500 boards succeeded and returned the same aggregate job count. The machine-readable reports and fixed input are included with the release artifacts.
Historical catalog candidates are never presented as verified companies. A candidate enters the live pilot only after its adapter returns a structurally valid response.
10,000-board catalog
Set catalog.enabled=true and catalog.bootstrapBundled=true to initialize the bundled
10,000-board candidate catalog. Its URLs were derived from versioned Common Crawl URL Index
snapshots; every new record starts as PENDING, not ACTIVE.
{"boards": [],"companyUrls": [],"catalog": {"enabled": true,"catalogId": "production-10k","bootstrapBundled": true,"process": true,"chunkSize": 100},"outputMode": "changes","watchlistId": "production-10k","includeDescriptions": false,"includeCompensation": false,"emitInitialSnapshotChanges": false,"maxConcurrency": 20}
The catalog uses PENDING, ACTIVE, RETRY, STALE, and EXPIRED states. Successful
fetches activate an entry. Failures get a retry deadline without advancing the job snapshot.
Repeatedly failing old entries become tombstoned EXPIRED records; a later company-domain
rediscovery can reactivate them as PENDING.
Every catalog run writes CATALOG_REPORT and CATALOG_TASK_PLAN. The task plan contains a
deterministic input for each chunk. A single rotating task does not provide ten-minute
freshness across 10,000 boards; create one exclusive scheduled task per chunk, then verify
account concurrency and CU cost in the cloud before enabling the full schedule.
StepStone scope decision
StepStone is intentionally not included as a ninth adapter. Its current general terms explicitly prohibit scraping content for another purpose, while its documented JobFeed API is a contracted customer/HR-tech interface for sending listings to StepStone rather than a public bulk-read API. The separate StepStone feasibility phase therefore triggered its predefined compliance stop-loss. No proxy, CAPTCHA bypass, or StepStone data collection code is shipped. Reconsider it only with written bulk-read/reuse permission or an appropriately licensed official read API.
Pricing
The billable job-result event is one record written to the default dataset. In changes
mode, unchanged jobs produce no result events at all - if nothing moved on a board, that run
costs you nothing beyond the Actor start fee.
This is verified behaviour, not a claim: a first observation of a board with 22 open roles billed 22 events; the very next run against the same board found no changes and billed zero.
What daily monitoring actually costs
At the current Free and Bronze event prices, a run using the default 512 MB memory costs
$0.005 to start plus $0.002 for each dataset row. In changes mode, the first run writes
the baseline and later runs write only changes. For one board with 500 open roles, checked
every day for 30 days with ten changes a day:
| Job or change events | Actor start | Total | |
|---|---|---|---|
| Day 1 baseline | $1.00 | $0.005 | $1.005 |
| Each later day | $0.02 | $0.005 | $0.025 |
| 30-day total | $1.58 | $0.15 | $1.73 |
The first run bills the initial 500 records. The following 29 runs bill only ten changed records each, plus the start event on every run. Plan discounts can reduce these prices. Put another way, the estimate for default-memory monitoring is:
monthly cost = ($0.005 x runs) + ($0.002 x baseline and change rows)
Buyer-scale planning examples
The rows below are calculator examples, not forecasts of how many jobs or changes a company will have. They assume one daily task at the default 512 MB memory, one baseline on day 1, 29 later runs, and the current Free/Bronze event prices. Board count is shown for planning, but billing follows Actor starts and emitted rows.
| Example cohort | Boards | Baseline rows | Change rows per later run | 30-day calculation | Estimated total |
|---|---|---|---|---|---|
| Agency pilot | 25 | 1,000 | 20 | (30 x $0.005) + ((1,000 + 29 x 20) x $0.002) | $3.31 |
| RPO client portfolio | 100 | 5,000 | 100 | (30 x $0.005) + ((5,000 + 29 x 100) x $0.002) | $15.95 |
| Market or investment research cohort | 500 | 25,000 | 500 | (30 x $0.005) + ((25,000 + 29 x 500) x $0.002) | $79.15 |
Replace every assumption with the result of a capped baseline and an observed week of changes before approving a production budget. Separate cohorts may require separate tasks, which add Actor-start events. Large watchlists also need an account-specific concurrency, runtime, and maximum-charge check.
Evaluate ROI without an invented savings claim
The Actor cannot prove the commercial value of a change signal for your organization. Use a measured break-even test instead:
break-even accepted signals = monthly Actor cost / measured value per accepted signal
Count only signals that a recruiter, analyst, CRM rule, or portfolio workflow actually accepts. If the comparison is manual research time, measure the current minutes per board and the fully loaded hourly cost first. Do not treat every emitted row as revenue or labor savings.
Apify's maximum total charge setting is respected, and the exact current prices are displayed in the Apify Store pricing section before every run. The Actor does not silently advance a board's snapshot after withholding some of its results for budget reasons.
Use cases
- Monitor target companies for new or closed roles
- Build a job-board feed from primary company sources
- Generate sales signals from hiring activity
- Track competitor headcount priorities
- Analyze role, location, salary, and remote-work changes
- Create DACH-focused job alerts for Ausbildung, Werkstudent, or Minijob roles
This Actor is also exposed to AI agents through Apify's MCP server mcp.apify.com: an agent can discover it by search and run it with the same pay-per-event billing, with no separate integration.
Data, privacy, and permissions
This Actor reads public company job postings only. It does not:
- log in to an ATS;
- access internal jobs, candidates, applications, resumes, or contact details;
- submit job applications;
- send source data to an external enrichment service.
For company-domain discovery, it requests at most discoveryMaxPages public HTML pages per
input URL. Private, local, credential-bearing, nonstandard-port, and private-DNS targets are
rejected to prevent server-side request forgery.
Inputs, outputs, and snapshots remain in the Apify storages associated with the user's account. Users are responsible for ensuring their use of public job data complies with applicable laws and source-site terms.
Troubleshooting and FAQ
Why did a successful run return zero dataset items?
In changes mode, zero items normally means nothing changed after the baseline. Unchanged
jobs are intentionally not written or billed again. Check RUN_SUMMARY to distinguish a
clean zero-change run from failed or budget-deferred boards.
Why was the first monitoring run larger than later runs?
With emitInitialSnapshotChanges=true, the first run creates the baseline and emits one
CREATED record for every currently open job. Later runs emit only real changes. Use the
maximum total charge setting to cap the baseline before starting a large watchlist.
How does the Actor prevent false job closures?
A board's snapshot advances only after a structurally valid fetch and complete output. A
timeout, rate limit, malformed response, or source failure keeps the previous snapshot and
emits no synthetic CLOSED events.
Which URL should I enter?
Use a supported ATS board URL or a public company home or careers page. Company-page discovery is bounded and can miss JavaScript-only or custom career sites. When that happens, provide the explicit ATS board URL shown in the supported-board table.
Where are the operational reports?
The default dataset contains jobs and change events. Run diagnostics are separate JSON
records in the default key-value store: RUN_SUMMARY, DISCOVERY_REPORT,
FRESHNESS_REPORT, QUALITY_REPORT, CATALOG_REPORT, and CATALOG_TASK_PLAN.
Does the Actor need an ATS login or candidate data?
No. It uses public company career pages and public job feeds only. The Actor runs with Limited permissions and does not access candidate profiles, applications, resumes, or private ATS accounts.
Is this suitable for a recruitment agency or RPO?
Yes, when the workflow starts from public employer career pages and uses job changes as
research or reconciliation inputs. Keep a separate watchlistId for each client or account
cohort. It does not provide candidate data, private requisitions, contact details, or proof
that a company is ready to buy recruitment services.
Can it monitor private equity or venture capital portfolio companies?
Yes, if the goal is to observe public portfolio-company career pages in a consistent schema. The output can show public openings and changes, but it is not a verified headcount feed, financial metric, valuation input, or investment recommendation.
How should I estimate a large watchlist before committing budget?
Run a capped baseline for a representative cohort, record baseline rows and a week of change rows, and put those observed values into the pricing formula. Then test the required task count and concurrency in your own Apify account. The maximum total charge protects the cap, and a board deferred for budget does not advance its snapshot.
Does failure-safe closure detection mean sources never fail?
No. Public career boards can time out, throttle requests, change format, or disappear. The
safety property is narrower: an invalid fetch keeps the last successful snapshot and does
not fabricate CLOSED events. Inspect boardsFailed, failedBoards, and
boardsSkippedForBudget in RUN_SUMMARY before consuming a run downstream.
Current limitations
- Company-domain discovery reads public static HTML. JavaScript-only sites, custom career stacks, or pages without a supported ATS link can remain unresolved; provide an explicit board URL in those cases.
- Company identity uses the discovered domain or normalized company name; legal-entity hierarchies and renamed/acquired companies can still require downstream overrides.
- Duplicate detection is conservative metadata, not destructive suppression. Same-titled requisitions can require downstream review.
- KldB, language, skill, location, and description-salary enrichment is deterministic heuristic metadata. It should be reviewed before regulated, statistical, or compensation decisions; the built-in city and skill dictionaries are intentionally bounded.
- Source providers can change their public formats; partial-run safety prevents a format failure from creating false closures.
- The bundled 10K catalog is a candidate inventory, not a claim that all 10,000 historical URLs are currently live. Lifecycle state and live fetch reports provide that distinction.
- Full 10K/10-minute operation requires multiple scheduled tasks and enough cloud concurrency. Local benchmarks cannot substitute for the final account-specific CU and pricing check.
- Snapshot writes are atomic per board, but dataset output and snapshot persistence cannot be committed as one cross-storage transaction. A crash between them can repeat an event.
Support and issue reports
Open an issue on the Actor page with:
- the public board URL and ATS provider;
- the run ID and displayed build number;
- expected behavior and actual behavior;
- the relevant
RUN_SUMMARYcounts and one sanitizedfailedBoardsentry; and - the smallest input that reproduces the problem.
Do not post API tokens, candidate information, private ATS data, client-confidential names, full account exports, or other secrets. If the client identity is confidential, reproduce the behavior with a public non-client board before using the public issue route. Failure-safe closure handling limits bad output; it is not a support SLA or a promise that every public source remains available.