Career Site Jobs Scraper - $0.55/1k, 10 ATS avatar

Career Site Jobs Scraper - $0.55/1k, 10 ATS

Pricing

from $0.55 / 1,000 job scrapeds

Go to Apify Store
Career Site Jobs Scraper - $0.55/1k, 10 ATS

Career Site Jobs Scraper - $0.55/1k, 10 ATS

10 ATS families sit behind most career pages, and this resolves a page URL, a bare domain or just a company name to whichever one it is. Every open job comes back: title, location, posted date, apply link and the ATS it resolved to. $0.55 per 1,000 jobs plus a $0.001 start fee.

Pricing

from $0.55 / 1,000 job scrapeds

Rating

0.0

(0)

Developer

Dami's Studio

Dami's Studio

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

2 days ago

Last modified

Categories

Share

Career Site Jobs Scraper

Give it a company career page, a bare domain, a job-board URL, or just a company name. You get back every open job on it: title, location, department, employmentType, postedAt, applyUrl, jobUrl, jobId, salary where the board publishes one, and the applicant tracking system it resolved to.

Nearly every careers page is a thin front end over an ATS, and those systems publish an unauthenticated JSON feed of open roles so the company's own page can render them. This reads that feed directly. Ten ATS families: Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Personio, Teamtailor, Breezy HR and Workday. No account, no cookies, no browser rendering.

The part that saves you work is the detection. Resolving a company to its job board normally means knowing up front that Figma hires through Greenhouse under the token figma and that Ramp uses Ashby. Fine for three companies, unusable for three hundred. Here you paste what you actually have and every row comes back stamped with the ats and boardToken it resolved to, so the first run doubles as a way to build that mapping for yourself.

Price

$0.55 per 1,000 jobs, plus a $0.001 start fee per run (billed per gigabyte of run memory, so exactly that on the default 1 GB).

The rate is the same on the free plan and on a paid one. Nothing is tiered by volume, and there's no minimum spend or add-on fee.

JobsTotal cost
100$0.056
1,000$0.551
10,000$5.501
100,000$55.001

What is actually charged

  • One job-scraped event per job row written to the dataset. Nothing else is metered per row.
  • Free: the sample row an empty run returns, and every diagnostic row — a blocked target, a dead URL, a company that matched nothing. Those all carry "charged": false.
  • A company whose career page exposes no supported feed costs you nothing. So does one whose board exists but lists no open roles.
  • Duplicate postings inside one company are dropped before they're charged.
  • Working out which system a company uses is free. You're billed for jobs, not for lookups.
  • A run that finds nothing costs the start fee and nothing more.
  • The billed event has its own name. Nothing is priced against apify-default-dataset-item, the catch-all the platform charges for any row written, so a diagnostic row can't quietly land on your bill.

Input

{
"companies": [
"https://www.figma.com/careers",
"https://jobs.ashbyhq.com/ramp",
"notion.com",
"Databricks"
],
"maxItems": 100
}
FieldWhat it does
companiesOne entry per company, up to 50 per run. A careers page URL, a bare domain, a board URL, or a company name. The run works out the rest.
maxItemsTotal jobs across all companies. The budget is split evenly, so five companies and 50 jobs gives you ten each. Default 50, hard ceiling 5,000. Keep it low while testing — you pay per job.
fullDetailsOff by default. Adds a plain-text description to every row, and fills in the posted date on Ashby rows. Slower and much heavier, because description feeds are many times larger than listing feeds.
proxyUrlsLeave empty. Fill it in only if you want the traffic to leave through proxy servers you already pay for, as http://user:pass@host:port.

Run it with empty input and you get one clearly labelled sample row, free, so you can see the output shape before you spend anything.

Output

One row per job. A real row from a real run:

{
"ok": true,
"charged": true,
"recordType": "job",
"inputUrl": "https://www.figma.com/careers",
"company": "figma",
"ats": "greenhouse",
"atsName": "Greenhouse",
"boardToken": "figma",
"title": "Software Engineer, Design Systems",
"location": "San Francisco, CA • New York, NY",
"remote": null,
"department": "Engineering",
"employmentType": null,
"postedAt": "2026-06-30T18:11:04.000Z",
"applyUrl": "https://job-boards.greenhouse.io/figma/jobs/5561234004",
"jobUrl": "https://job-boards.greenhouse.io/figma/jobs/5561234004",
"jobId": "5561234004",
"salary": null,
"detectedVia": "token",
"scrapedAt": "2026-08-15T16:42:08.911Z"
}

Field notes

  • inputUrl — the exact string you supplied for this company, so you can join the rows back onto your input list.
  • ats — the applicant tracking system detected, lower-case and stable: greenhouse, lever, ashby, workable, smartrecruiters, recruitee, personio, teamtailor, breezy, workday. atsName is the same thing spelled for humans.
  • boardToken — the company's identifier on that system. Handy if you want to hit the same board again later without re-detecting it.
  • location — as the company wrote it. Some boards give one city, some give a list, some give a region like "Europe". Multiple locations are joined with commas.
  • department — the team or department the board files the job under. A few systems don't expose it — see Limitations.
  • employmentType — full-time, part-time, contract, intern and so on, in the wording the board uses. Null where the board doesn't record it.
  • postedAt — when the posting was published, normalised to ISO 8601 in UTC. Null where the board doesn't publish a date.
  • applyUrl — the page that starts an application. On some systems that is the posting page itself.
  • jobUrl — the public posting page, suitable for sharing.
  • jobId — the posting's id on its board. Stable, and the right key to diff on when you re-run.
  • remote — true or false where the board records it explicitly, otherwise inferred from the location text, otherwise null.
  • salary — only where the board publishes a range. Most don't.
  • detectedVia — how the board was found: url (your input already named it), token (matched from the company domain or name), or html (read off the careers page).

Every real row carries "charged": true. Sample rows carry "_sample": true and diagnostic rows carry "_diagnostic": true with an errorCode you can filter on, and neither is ever billed.

How it works

Detection runs cheapest-first. If the URL you gave already names a board, that costs nothing. Otherwise the run derives a likely board identifier from the domain or company name and asks the boards directly — a miss is a tiny 404, far cheaper than downloading a corporate marketing page. Only if both fail does it fetch the careers page itself and look for an embedded board.

There's no page rendering anywhere in that chain, because the pages in question are themselves just calling the feeds already being read. Each feed is requested in its lightest documented form: where a system offers both a listing feed and a much larger one with every description inlined, the listing feed wins unless you asked for descriptions.

Requests leave through a rotating pool of datacenter addresses, so a per-address rate limit is answered by moving rather than by waiting.

Reading the output

Every run writes three kinds of row, and they are easy to tell apart:

  • Real rows carry "charged": true and "recordType": "job". These are the rows you paid for, one billed event each.
  • The sample row carries "_sample": true and "charged": false. There's exactly one, it only appears when the input had no companies in it, and it exists so you can look at the output shape before you spend anything.
  • Diagnostic rows carry "_diagnostic": true, "charged": false and an errorCode you can switch on: NO_RESULTS when a company has no supported feed or no open roles, NOT_FOUND when a board id turned out not to exist, RATE_LIMITED when a board throttled the run, SERVER_ERROR when a board was briefly down, NETWORK when it could not be reached at all, and TIME_BUDGET when the run ran out of time before reaching a company. Each one carries the inputUrl it belongs to and a plain-English explanation.

If you only want the data, filter on charged == true. The count of those rows always equals the number of events you were billed for, so the dataset is its own invoice.

Getting the best coverage

If a company comes back as NO_RESULTS and you believe it does publish jobs, the fastest fix is to give the board URL directly. Open the company careers page, click through to any single job, and look at the address bar — if it lands on a job-board host, paste that URL in instead of the marketing page and the run will use it verbatim.

Workday boards must always be supplied as a URL, because a Workday address contains a tenant, a datacenter and a site name that can't be guessed from a company name. Any Workday careers URL works — the run pulls the three parts out of it.

Limitations

  • Only career pages running on Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Personio, Teamtailor, Breezy HR and Workday are supported. Large enterprises frequently use systems that publish no open job feed at all; those companies return an uncharged diagnostic row rather than data.
  • A company is only found if its board identifier can be derived from its domain or name, or is present on its careers page, or you supplied the board URL yourself. A company whose board id bears no relation to its name and who doesn't embed the board on a fetchable page won't resolve.
  • Workday needs a full careers URL, not a company name — the address contains a tenant and site id that can't be guessed.
  • Posted dates aren't universal. Ashby only exposes them on the heavier feed, so they arrive null unless you turn on full details; Personio doesn't publish them at all; Workday reports them as relative text ("Posted 30+ Days Ago") which is converted to an approximate date, not an exact one.
  • Department isn't universal either. Workday and Teamtailor listing feeds don't carry it at all, SmartRecruiters carries it on roughly a third of postings, and Personio boards that don't categorise their roles leave it empty.
  • Employment type is null on Greenhouse and Workday, which don't carry it in their public listing feeds.
  • Salary appears only where the board publishes a range. The large majority don't.
  • Job descriptions are off by default. Turning them on makes runs slower and much heavier, and the text is flattened to plain text with the HTML stripped.
  • Results are the boards' current open roles. There's no history and no closed-job archive; a posting that was filled yesterday is simply absent today.
  • Boards rate-limit by address under heavy use. The run rotates addresses and retries, but a very large company list in a single run can still hit a throttle and return diagnostic rows for the tail.
  • The time spent working out which system a company uses is paid for by the jobs the run finds, so a run that finds nothing gives up quickly: if the first companies in your list can't be resolved, the rest come back as uncharged TIME_BUDGET rows within a few seconds rather than the run grinding through all fifty. That keeps a list of dead URLs from eating your run time, and it never happens once jobs start coming back. Putting a board URL first, or supplying board URLs throughout, sidesteps it entirely — a board URL needs no lookup at all.
  • Location is whatever the board recorded, which isn't normalised across systems — one company writes "Remote - US", another writes "San Francisco, California, United States".
  • The hard ceilings are 50 companies and 5,000 jobs per run. Split larger jobs across runs.

Questions

What do I actually paste in?

Whatever you have. A careers page URL, a bare domain like figma.com, a board URL like https://jobs.lever.co/matchgroup, or just Figma. All four resolve to the same board. A URL is more reliable than a name, and a board URL is the most reliable of all.

How do I know which system a company uses?

You don't need to — that is the point. Every row tells you, in the ats and boardToken fields. Run your list once and you have the mapping for good.

What happens if a company isn't supported?

You get one uncharged diagnostic row for it with errorCode: "NO_RESULTS" explaining what was tried, and the run carries on to your other companies. You're never billed for a company that returned no jobs.

Why is postedAt sometimes null?

Because the board doesn't publish it. Different systems expose different fields, and inventing a date would be worse than leaving it empty. The Limitations section lists exactly which systems omit it.

Can I get the full job description?

Yes — turn on full details. It's off by default because the description feeds are many times larger than the listing feeds, so leaving it on makes every run slower for data most callers don't use.

Do I need a proxy?

No. The run brings its own egress, and that cost is already inside the price you see. The proxyUrls field exists only for callers who specifically want traffic to leave through servers they already own.

Will the run fail if one company breaks?

No. A blocked, empty or unsupported company produces an uncharged diagnostic row and the run carries on and still finishes as succeeded. A failed run would still bill you the start fee, which would mean paying to be told something went wrong.

Can I run this on a schedule?

Yes. Nothing in the run holds state between runs, so a schedule with the same company list is safe. Diff on jobId to see which postings are new since last time.

How do I get exactly the rows I paid for?

Filter the dataset on "charged": true. Sample and diagnostic rows are always false, and the number of charged rows always equals the number of billed events.