ATS Jobs Scraper (All In One) avatar

ATS Jobs Scraper (All In One)

Pricing

from $0.56 / 1,000 results

Go to Apify Store
ATS Jobs Scraper (All In One)

ATS Jobs Scraper (All In One)

Scrapes live jobs from four ATS platforms — Ashby, BambooHR, Breezy HR and SmartRecruiters — into one unified schema. Paste mixed career page URLs and each is routed automatically. Split city/state/country, ISO 8601 UTC dates, and descriptions as clean Markdown for LLM use.

Pricing

from $0.56 / 1,000 results

Rating

0.0

(0)

Developer

Ibnu Adzim

Ibnu Adzim

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

11 days ago

Last modified

Share

All-in-One ATS Jobs Scraper

Scrapes live job postings from four applicant tracking systemsAshby, BambooHR, Breezy HR and SmartRecruiters — and returns them in one unified schema.

Paste a mixed list of career page URLs. The Actor works out which ATS each one belongs to from the URL and routes it automatically, so you never have to sort your list by platform or run four different scrapers and reconcile the output yourself.

Public data only. No login, no cookies, no browser, and no bot challenge on any of the four — every one of 8 TLS fingerprints tested returned a clean 200 from an ordinary residential connection.

Why one Actor instead of four

The four platforms differ in transport — how they paginate, whether descriptions arrive with the listing or need a second request, what they do when a company does not exist. They do not differ in purpose: each is a company-scoped public job board.

Which ATS a company runs is an implementation detail its candidates never chose and should not have to care about. If you are tracking 200 companies, some are on Ashby and some are on BambooHR, and what you want is one table. Reconciling four location shapes, four employment-type vocabularies and four timestamp formats is the hard part of this job — so the Actor does it, once, rather than handing you the same problem four times.

Supported URL forms

ATSCareer page URLSingle job URL
Ashbyhttps://jobs.ashbyhq.com/posthoghttps://jobs.ashbyhq.com/posthog/<uuid>
BambooHRhttps://nectar.bamboohr.com/careershttps://nectar.bamboohr.com/careers/157
Breezy HRhttps://boldare.breezy.hrhttps://boldare.breezy.hr/p/<id>
SmartRecruitershttps://jobs.smartrecruiters.com/BoschGrouphttps://jobs.smartrecruiters.com/BoschGroup/<id>

A URL naming one job scrapes just that posting. The shorthand platform:company works too — ashby:posthog, bamboohr:nectar, breezy:boldare, smartrecruiters:BoschGroup.

If a company's careers page is on its own domain (careers.acme.com), it is usually just embedding one of these four. Use the ATS URL its Apply button points at.

What you get

Three record types share one dataset, told apart by recordType.

JOB — one row per posting

Every job row has the same fields regardless of which ATS it came from:

{
"atsPlatform": "ashby",
"companySlug": "vanta",
"companyName": "Vanta",
"jobId": "f8af3807-3595-4580-a65c-dad2e268ace5",
"title": "Technical Account Executive - EMEA",
"jobUrl": "https://jobs.ashbyhq.com/vanta/f8af3807-…",
"applyUrl": "https://jobs.ashbyhq.com/vanta/f8af3807-…/application",
"department": "Sales & Customer Success",
"team": "New Business Sales",
"employmentType": "FULL_TIME", // FULL_TIME | PART_TIME | CONTRACT
// | INTERNSHIP | TEMPORARY
// | VOLUNTEER | OTHER
"workplaceType": "REMOTE", // REMOTE | HYBRID | ONSITE
"location": {
"raw": "Remote U.S.",
"city": null,
"state": null,
"country": "United States",
"countryCode": "US", // ISO 3166-1 alpha-2
"postalCode": null,
"latitude": null,
"longitude": null,
"isRemote": true
},
"secondaryLocations": [ /* same shape */ ],
"publishedAt": "2026-06-12T09:30:02Z", // ISO 8601, always UTC
"updatedAt": null,
"compensation": { "raw": "…", "minimum": 150000, "maximum": 190000,
"currency": "USD", "interval": "YEARLY" },
"descriptionHtml": "<h2>About…",
"descriptionMarkdown": "## About…", // clean Markdown, for LLM/RAG use
"descriptionText": "About…",
"isListed": true,
"raw": { /* upstream's object, verbatim */ }
}

raw keeps the untouched upstream object, so nothing is lost to normalisation and you can always reach a platform-specific field the unified schema does not model.

BOARD_SUMMARY — one row per career page

The board's own job count, how many rows this run took, how many requests it cost, and the honesty flags below.

ERROR — one row per input that failed

Every input URL produces at least one row, so a board that 404s or a URL that is not an ATS at all is visible in the dataset rather than silently missing.

Descriptions as Markdown

ATS job descriptions are HTML written in rich-text editors: inline styles, spacer paragraphs, nested lists. descriptionMarkdown is that HTML converted to clean Markdown with ATX headings, which is what makes this dataset directly usable as LLM input without a second cleaning pass.

On SmartRecruiters the description arrives as four separate blocks — company description, job description, qualifications, additional information. They are concatenated in the order the real job page shows them, each under its own heading, rather than being flattened into one undifferentiated wall of text.

Cost: the includeDescription switch

This is the Actor's main cost lever, and it behaves differently per platform:

ATSRequests with descriptionsRequests without
Ashby2 per board, any number of jobs1 per board
BambooHR1 + 1 per job1 per board
Breezy HR1 + 1 per job1 per board
SmartRecruiters1 per 100 jobs + 1 per job1 per 100 jobs

Ashby ships the full description inside the board response, so descriptions there are effectively free.

One caveat: turning descriptions off on BambooHR also drops location.country and publishedAt, because that platform only returns them on the per-job response.

Known limits and honest flags

A nonexistent SmartRecruiters company looks exactly like a real one with no open jobs. It answers totalFound: 0 either way, and no other endpoint distinguishes them — jobs.smartrecruiters.com/<company> and careers.smartrecruiters.com/<company> redirect identically for a real-but-empty company and a misspelt one. Rather than report a confident "0 jobs", the summary row sets companyExistenceUnverified: true and the run logs a warning. The other three platforms are honest: Ashby and Breezy return 404, BambooHR redirects away.

workplaceType is left null rather than guessed. Some Ashby postings carry no workplace field at all; where the location text does not clearly say remote or hybrid, the Actor emits null instead of assuming on-site.

BambooHR exposes no company display name. Its API has no such field and every tenant's careers page is titled "BambooHR", so companyName falls back to the URL slug there. Ashby's name is read from the board page and falls back to the slug on the minority of tenants that serve an un-rendered page.

pageSize only affects SmartRecruiters. It is the only one of the four that paginates; the others return their whole board in a single response. It is capped at 100 because SmartRecruiters silently clamps anything higher back to 100 while still reporting success.

Input

FieldTypeDefaultNotes
startUrlsarrayRequired. Mixed career page URLs.
includeDescriptionbooleantrueFull descriptions + Markdown.
maxItemsinteger0Jobs per board; 0 = all.
pageSizeinteger100SmartRecruiters only. Max 100.
maxConcurrencyinteger6Total requests in flight.
minRequestIntervalinteger0Seconds between request starts.
proxyConfigurationobjectResidentialNot needed to get past a bot wall — there isn't one.

Notes on politeness

None of these platforms rate-limited during testing, but many of these boards belong to small companies rather than enterprises. The defaults are modest on purpose, and minRequestInterval — not maxConcurrency — is the honest speed control: once a rate cap binds, extra concurrency buys nothing.

See CRAWLING_METHOD.md for the full recon trail, the endpoints, and every trap found while building this.