Skip to main content
Thirdwatchthirdwatch
Hiring & talent

Scrape Ashby, Workday, and Recruitee Career Pages

Collect direct-employer jobs across modern ATS career pages with one Apify actor and a stable deduplication workflow.

Editorial illustration for hiring & talent
Jul 21, 2026 · 2 min read · 421 words
View the Apify scraper →

Employer career pages contain the cleanest version of a job: the direct apply link, the employer's wording, and the listing's current availability. The nuisance is that every applicant-tracking system exposes the data differently.

The Multi-ATS Jobs Scraper handles supported ATS career boards behind one input and returns one schema. It covers Ashby, Workday, Recruitee, Greenhouse, Lever, SmartRecruiters, Workable, and BambooHR.

Start with public career-board URLs

List the boards of the employers you want to track in boardUrls. Mix systems freely, and use the platform:slug short form when it is easier than a full URL:

{
  "boardUrls": [
    "https://jobs.ashbyhq.com/linear",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://bunq.recruitee.com",
    "lever:spotify"
  ],
  "maxResults": 400,
  "maxResultsPerBoard": 100,
  "includeDescription": true
}

Workday boards are addressed by three parts (tenant, shard and site name). Pasting the full board URL is the easiest option; the short form is workday:nvidia/wd5/NVIDIAExternalCareerSite.

Keep the employer-to-URL mapping in your own table. Companies migrate between ATS vendors and sometimes change their career subdomain without redirecting the old page.

Normalize after collection

The actor already maps every board into the same keys (department, employment_type, location, city, country_code, posted_at and so on), but ATS platforms still disagree on the values: department names, location formatting, and employment-type labels such as FullTime versus Full-time. Preserve the raw fields, then add normalized columns for search and reporting.

def job_key(row):
    if row.get("job_id"):
        return f'{row["platform"]}|{row["company_slug"]}|{row["job_id"]}'
    if row.get("job_url"):
        return row["job_url"].split("?")[0].rstrip("/")
    return "|".join(
        str(row.get(k, "")).strip().lower() for k in ("company", "title", "location")
    )

Avoid deduplicating on title alone. A company may have the same role in several cities, and remote listings may intentionally repeat by country.

Detect new and removed openings

Save a snapshot of stable job keys for each employer. On the next run, current - previous gives new jobs and previous - current gives removed jobs. Mark removals as no longer observed instead of deleting them immediately; temporary ATS outages do happen.

For hiring-velocity analysis, count openings and closures by employer and week. Do not interpret a one-day drop as a layoff signal without checking the source page. Career-site changes can reflect requisition housekeeping or a platform migration.

Keep failure states visible

Record when each employer was checked and whether extraction succeeded. "No jobs" and "page could not be read" must remain different states. This is especially important in aggregators, where an empty successful response is legitimate for a small company.

The Multi-ATS Jobs Scraper page has field details and ready-to-run examples. Begin with two employers from different ATS platforms, inspect the normalized output, then expand your watchlist.

Frequently asked questions

Do I need to identify the ATS first?

No. Paste the public career-board URLs into boardUrls and the actor recognises which supported ATS is behind each one. You can also use short forms such as ashby:linear or recruitee:bunq.

Can I scrape several employers in one run?

Yes. Add each public board URL to boardUrls. Every row carries company, company_slug and board_url, so you can label results by employer without extra input. Use maxResultsPerBoard to stop one large board from using the whole maxResults budget.

How should I deduplicate jobs?

Prefer the stable posting ID: platform, company_slug and job_id together, or the canonical job_url. Fall back to a normalized company, title, and location tuple only when neither is available.

Related