ATS Job Change Feed API - Greenhouse + 7 More avatar

ATS Job Change Feed API - Greenhouse + 7 More

Pricing

from $1.60 / 1,000 job or change records

Go to Apify Store
ATS Job Change Feed API - Greenhouse + 7 More

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

Kamer Ozkan

Maintained by Community

Actor 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

GoalStart with this live ExampleOutputRecommended run pattern
Trigger CRM or account-research workflows from hiring changesTrack competitor hiring changesOnly CREATED, UPDATED, and CLOSED events after the baselineDaily or every 10 minutes
Build a normalized job-alert or recruiting-analytics feedBuild a multi-ATS job change feedStable job IDs, provenance, and lifecycle eventsScheduled
Export current jobs from company career pagesScrape company career pagesCurrent normalized job rowsOn demand
Analyze DACH skills, languages, locations, and salary signalsTrack DACH job market signalsCurrent jobs plus DACH enrichmentDaily 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.

BuyerOperational workflowEvidence to route downstreamBoundary
Recruitment agency or executive search teamMonitor target employers and existing client career pages for role openings and closuresCREATED, UPDATED, and CLOSED events with company, role, location, source URL, and timestampsA hiring change is a prospecting signal, not proof that an employer will buy recruitment services
RPO or MSP teamReconcile public client requisitions across supported ATS career pages and flag lifecycle changes for reviewStable job IDs, explicit change type, and RUN_SUMMARY board outcomesThis does not read private requisitions, candidates, applications, or the client's ATS account
Recruitment market intelligence teamBuild a source-direct time series by role, location, skill, language, salary signal, or ATSNormalized records, provenance, duplicate markers, and change timestampsEnrichment is deterministic heuristic metadata and needs review before regulated or statistical use
Private equity, venture capital, or portfolio operations teamMonitor public portfolio-company and target-company hiring activity in separate cohortsCurrent jobs plus new, edited, and closed role eventsPublic 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 scraperATS Job Change Feed API
Returns every open job againEmits only real changes in changes mode
Downstream system must infer closuresEmits explicit CLOSED events
Empty response can look like mass closureFailed or malformed sources retain prior state
One ATS-specific schemaOne schema across eight ATS formats
Requires known ATS URLsCan discover boards from company websites
Generic locations and textDACH locations, KldB, languages, skills, and salary signals
Duplicate suppression is opaqueCross-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, and MINIJOB;
  • 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 os
from apify_client import ApifyClient
client = 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().items
print(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

ATSAccepted board URL
Greenhousehttps://job-boards.greenhouse.io/{company}
Greenhouse legacyhttps://boards.greenhouse.io/{company}
Leverhttps://jobs.lever.co/{company}
Lever EUhttps://jobs.eu.lever.co/{company}
Ashbyhttps://jobs.ashbyhq.com/{company}
Workablehttps://apply.workable.com/{company}
Recruiteehttps://{company}.recruitee.com
Personiohttps://{company}.jobs.personio.de
Teamtailorhttps://{company}.teamtailor.com
Workdayhttps://{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

FieldTypeDefaultDescription
boardsarrayGreenhouse example boardBoard URL strings or { url, ats, slug, company } objects
companyUrlsstring[][]Company home or careers URLs to scan for supported ATS boards
catalogobjectdisabledBootstrap/process the bundled 10K candidate catalog and configure chunk/lifecycle rules
outputModestringchangeschanges, jobs, or both
watchlistIdstringdefaultUser-chosen snapshot namespace; use a unique value per independent watchlist
includeDescriptionsbooleantrueReturn descriptions and detect description edits
includeCompensationbooleantrueNormalize salary data when exposed by the ATS
emitInitialSnapshotChangesbooleantrueEmit CREATED for every job on the first run
maxConcurrencyinteger10Boards fetched in parallel, from 1 to 50
requestTimeoutSecsinteger30Timeout for each request attempt
maxRequestRetriesinteger2Retries for rate limits, server errors, and network failures
failOnBoardErrorbooleanfalseFail the run instead of returning a partial result
discoveryMaxPagesinteger5Maximum public pages scanned for each company URL
failOnDiscoveryErrorbooleanfalseFail if any company URL cannot be resolved
freshnessSlaMinutesinteger10Source-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 complete firstSeenAt / lastSeenAt history, so downstream systems never have to infer closures from absence.
  • No stale content. Stored snapshots deliberately drop descriptions, so a CLOSED record reports them as null instead of replaying outdated text.
  • No false mass closures. A failed, throttled, or malformed board keeps its previous snapshot and emits no CLOSED events 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 CLOSED events.
  • 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 eventsActor startTotal
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 cohortBoardsBaseline rowsChange rows per later run30-day calculationEstimated total
Agency pilot251,00020(30 x $0.005) + ((1,000 + 29 x 20) x $0.002)$3.31
RPO client portfolio1005,000100(30 x $0.005) + ((5,000 + 29 x 100) x $0.002)$15.95
Market or investment research cohort50025,000500(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_SUMMARY counts and one sanitized failedBoards entry; 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.