Job Scraper for Greenhouse, Lever & Ashby
Pricing
from $0.30 / 1,000 jobs
Job Scraper for Greenhouse, Lever & Ashby
Collect jobs from public Greenhouse, Lever and Ashby feeds or JobPosting JSON-LD. Get normalized job records, deduplicated by application URL.
Give your agent comparable job records without writing a parser for each careers site. Send public ATS identifiers or careers URLs for normalized jobs, including structured salary data when available. The Actor reads public feeds or JobPosting JSON-LD. It returns no job descriptions. Each delivered Job costs $0.0003, plus $0.00005 per run start at default memory.
Try it
{ "inputs": ["lever:leverdemo"] }
An empty input {} runs this same public Lever demo board. No upstream API credentials are needed.
Use from an agent
Add https://mcp.apify.com?tools=miacx/job-scraper-greenhouse-lever-ashby to your MCP client and sign in to Apify.
Call the dedicated Actor tool, then use get-dataset-items to read the completed run's dataset.
Or set APIFY_TOKEN to your Apify API token and call the API for JSON rows:
curl --fail-with-body 'https://api.apify.com/v2/actors/miacx~job-scraper-greenhouse-lever-ashby/run-sync-get-dataset-items' \-H "Authorization: Bearer $APIFY_TOKEN" -H 'Content-Type: application/json' \-d '{"inputs":["lever:leverdemo"]}'
What you get
Read status first, then check for applyUrl. An ok job row is billable; an ok empty-board row is not.
The dataset contains unique jobs, deduplicated by application URL across the run, plus uncharged error and empty-board rows.
An ATS job URL requests its whole board. Other careers pages provide one discovery page or their own JobPosting JSON-LD.
This job row comes from the recorded Lever fixture. The fetch timestamp is fixed for the offline replay:
{"status": "ok","id": "ad208490-4052-4f91-a57e-433f2d1e484b","title": "Customer Success Manager","team": "Customer Success Team","locations": ["Atlanta, Georgia"],"workMode": "hybrid","employmentType": "full_time","salaryMin": 85000,"salaryMax": 175000,"salaryCurrency": "USD","salaryInterval": "year","postedAt": "2026-08-31T18:12:19.703Z","applyUrl": "https://jobs.lever.co/leverdemo/ad208490-4052-4f91-a57e-433f2d1e484b/apply","ats": "lever","source": "https://api.lever.co/v0/postings/leverdemo?mode=json","fetchedAt": "2026-10-31T12:00:00.000Z"}
Job rows contain no input or message. Status-only rows contain these fields and omit job fields.
Empty-board rows use message: "Board contains no current jobs."
Missing job values stay null; unknown locations are []. Location strings retain source order with duplicates removed.
workMode comes from explicit fields or exact location labels, never general description text.
Salary uses the first supported structured base-salary range in its original currency and period, excluding bonus and equity.
postedAt uses a publication or creation date, never an update date.
source identifies the feed or page; fetchedAt records its fetch time.
The default key-value store also contains OUTPUT, an ordered per-input summary.
It preserves duplicate inputs and marks boards that could not be fully delivered with BILLING_LIMIT.
Input
inputs is a list of 1–25 strings, each at most 2,048 characters.
Omitting it uses ["lever:leverdemo"]; an empty list fails the run. Bare company names are invalid.
Use public HTTP(S) URLs or qualified identifiers. Identifier company slugs allow letters, digits, underscores and hyphens, up to 100 characters.
| Prefix | Source | Coverage |
|---|---|---|
greenhouse: | Greenhouse public JSON | Also recognizes EU and US board/job URLs. |
lever:, lever-eu: | Lever public JSON | Also recognizes EU and US board/job URLs. |
ashby: | Ashby public job-board API | Includes available structured compensation. |
workable: | Workable public account JSON | Uses the public company feed. |
recruitee: | Recruitee Careers Site API | Adapter follows the public contract; its fixture is synthetic. |
smartrecruiters: | SmartRecruiters postings JSON | Current recorded robots rules block generic crawlers; requests return ROBOTS_DENIED. |
personio: | Personio public XML | Also recognizes .de and .com board URLs. |
A careers page can select one linked ATS feed. A selected feed error returns directly without a JSON-LD fallback. If no supported feed is found, the Actor reads JobPosting JSON-LD from that page and skips expired postings. It does not crawl linked job-detail pages or render scripts.
Pricing
| Event | Price | Counted unit |
|---|---|---|
| Job | $0.0003 | One normalized job delivered to the dataset, deduplicated by application URL across the run. |
| Actor start | $0.00005 | One run start at the default 256 MB memory. |
One hundred delivered jobs cost $0.03005 including startup at default memory. Missing optional fields do not make a job free. Repeated inputs and shared application URLs incur no additional Job events. Errors and empty-board status rows incur no Job events. Delivered jobs remain billable if another board fails or a spending limit prevents the rest of a board's output. The $0.00005 per run start at default memory applies even with no billable jobs. Apify counts one startup event per GB of memory, with a minimum of one event.
Limits and data freshness
- Up to 2,000 jobs per board, three concurrent inputs and 40 outbound requests per batch, including DNS, robots checks and retries.
- Responses allow 2 MiB each and 16 MiB across the batch. Large or changing paginated boards return explicit errors.
- The Actor shares duplicate work within a run and has no cross-run response cache. Each new run fetches the permitted sources.
- Requests respect robots.txt and crawl delays. Private-network destinations are blocked; access denials are not bypassed.
- No applicant records, job descriptions, browser rendering or runtime LLM calls are included.
The synchronous API waits up to 300 seconds. Use asynchronous runs when coordinating longer workflows.
If a row fails
| Status | Next step |
|---|---|
INVALID_INPUT, UNSUPPORTED_SOURCE, UNSAFE_URL | Supply a supported identifier or public careers URL. |
ROBOTS_DENIED | Use a source that permits automated access. |
ROBOTS_UNAVAILABLE | Retry when a usable robots policy is available. |
NO_JOBS_FOUND | Check for a supported feed or current JobPosting JSON-LD; scripts are not rendered. |
DNS_ERROR, NETWORK_ERROR, UPSTREAM_HTTP, RATE_LIMITED | Read message and retry transient failures later. |
RESPONSE_TOO_LARGE, JOB_LIMIT, REQUEST_LIMIT | Split the input batch; oversized individual boards remain unsupported. |
INVALID_RESPONSE | Retry a changing board, or retain the run ID when reporting malformed source data. |
BILLING_LIMIT | Raise the spending limit. The dataset contains only delivered jobs; incomplete boards have no unpaid jobs in OUTPUT. |
INTERNAL_ERROR | Retain the run ID and message when reporting the failure. |