The goal
You have a list of company domains — prospects, competitors, a market map. You want a feed of their open roles, refreshed weekly, with a diff of what changed. No vendors, no servers, no keys.
Two Actors do it.
Step 1 — domain in, hiring board out
The Domain Contact Enricher reads a company's site and detects which ATS it uses. It emits a ready-to-use input:
{
"domain": "example.com",
"atsProvider": "greenhouse",
"atsJobScraperInput": "greenhouse:example",
"technologyIds": ["nextjs", "cloudflare"],
"emailInfrastructure": { "mailProvider": "Google Workspace", "hasSpf": true, "hasDmarc": true }
}
Run it over your whole list:
{ "domains": ["stripe.com", "shopify.com", "tesla.com"], "maxPagesPerDomain": 3 }
Collect the atsJobScraperInput values that are not null.
Step 2 — boards in, job rows out
Feed those strings into the ATS Job Scraper:
{
"boards": ["greenhouse:stripe", "ashby:linear"],
"maxJobsPerBoard": 200,
"postedAfter": "14 days"
}
You get one normalised row per job with salary.evidence, workplaceType, postedAt and two change-detection keys:
-
jobUid— stable identity → spot new and removed roles, -
recordHash— content hash → spot edited postings.
Step 3 — schedule and diff
Add an Apify Schedule (weekly), then diff consecutive runs:
-
postedAfter: "14 days"keeps each run small and current. - New
jobUid= new role = a sales or talent signal. - Changed
recordHashon an old role = the description or salary moved.
If you want emails alongside the jobs, the Domain Enricher already returned contacts with their source page, so the same run gives you "who to talk to" and "what they are hiring for".
Why this beats a scraping stack
- No API keys and no logins anywhere in the chain.
- The actors report per-domain and per-board failures as error rows, so one dead board never fails the run.
- Both are pay-per-result: a 50-domain, 500-job refresh costs cents.
Start with the Domain Contact Enricher, then chain the ATS Job Scraper.
Top comments (0)