ATS Jobs Feed: Greenhouse & Lever Job Postings
Pricing
from $1.50 / 1,000 job delivereds
ATS Jobs Feed: Greenhouse & Lever Job Postings
Get job postings from company Greenhouse and Lever boards through their official public APIs (no login, no API key), normalized to one clean JSON shape, with new and closed job detection between runs. Polite and robots.txt-aware.
Pricing
from $1.50 / 1,000 job delivereds
Rating
0.0
(0)
Developer
William Schultz
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
20 hours ago
Last modified
Categories
Security
Limited permissionsShare
What does it do?
It reads the public job boards of the companies you list and returns one clean JSON item per open job, in the same shape for both systems. It also remembers which jobs were on each board last time, so each job is marked new, existing or closed.
Change tracking and the cap: with
changesOnlyon,maxJobsPerBoardcaps the number of changes per run, not the board. The first run records every job on the board, delivers up to the cap asnewand records the rest silently (not output, not charged). After that you get, and pay for, only jobs that really appeared or closed since the last run.
- Greenhouse: the official Job Board API (
boards-api.greenhouse.io/v1/boards/{token}/jobs). - Lever: the official Postings API (
api.lever.co/v0/postings/{company}, orapi.eu.lever.cofor EU-hosted boards).
Both APIs are public and read-only. The Actor uses no login, no API key and no account on either system, and it only reads what the company already publishes on its careers page. Ashby, Workday and other systems are not supported.
Who is it for?
Recruiting and sales-intelligence teams tracking who is hiring, job-board and newsletter builders, and AI agents or automations that need a structured "what changed on these careers pages since yesterday" feed. Run it on a schedule with change tracking turned on to get only new and closed postings.
Input
| Field | Default | What it does |
|---|---|---|
boards (required) | prefill greenhouse:airbnb, lever:spotify | Board URLs (job-boards.greenhouse.io/stripe, boards.greenhouse.io/stripe, jobs.lever.co/spotify, jobs.eu.lever.co/company) or greenhouse:token, lever:company, lever-eu:company. Duplicates are fetched once. |
maxJobsPerBoard | 500 (prefill 20, max 5,000) | Default mode: most open jobs delivered per board after filters. With changesOnly: most changes (new + closed) delivered per board per run; changes over the cap wait for the next run. Either way this caps cost. |
keywords | none | Keep jobs whose title, department or team contains any of these words (case-insensitive). |
locations | none | Keep jobs whose location contains any of these (case-insensitive). |
remoteOnly | false | Keep only jobs the board marks as remote. |
includeDescription | false | Add the description as plain text, up to 5,000 characters, with email addresses and phone numbers replaced by [redacted]. Descriptions may contain names from the original posting. |
changesOnly | false | Output only jobs that are new or closed since the previous run with the same state key. |
stateKey | derived from boards and filters | Name of the saved state used for change detection. |
leverEu | false | Use Lever's EU API for every lever: board. |
Example:
{"boards": ["https://job-boards.greenhouse.io/airbnb", "https://jobs.lever.co/spotify"],"maxJobsPerBoard": 20,"keywords": ["engineer", "data"],"changesOnly": false}
Output
One item per job. Real items from a local test run on 2 Nov 2026 (second run, so both are existing):
[{"source": "greenhouse","board": "greenhouse:airbnb","company": "Airbnb","jobId": "8306554","title": "AirCover Specialist, Claims Experience","department": "Community Support","team": null,"location": "Canada","remote": true,"employmentType": null,"url": "https://careers.airbnb.com/positions/8306554?gh_jid=8306554","applyUrl": "https://careers.airbnb.com/positions/8306554?gh_jid=8306554","postedAt": "2026-10-28T14:52:05.000Z","updatedAt": "2026-10-28T14:52:05.000Z","changeType": "existing","firstSeenAt": "2026-11-03T01:02:28.641Z","checkedAt": "2026-11-03T01:02:32.997Z","error": null,"errorCode": null,"notice": "Public job postings from the company's official board API. Not affiliated with Greenhouse or Lever. Not for AI training."},{"source": "lever","board": "lever:spotify","company": "spotify","jobId": "2a55d6c9-c564-443a-be3e-a3851db5a93b","title": "Account Executive - Commercial, Spotify Technology","department": "Operations and Business Support","team": "Platform","location": "New York, NY","remote": false,"employmentType": "Permanent","url": "https://jobs.lever.co/spotify/2a55d6c9-c564-443a-be3e-a3851db5a93b","applyUrl": "https://jobs.lever.co/spotify/2a55d6c9-c564-443a-be3e-a3851db5a93b/apply","postedAt": "2026-10-23T15:00:40.768Z","updatedAt": null,"changeType": "existing","firstSeenAt": "2026-11-03T01:02:28.641Z","checkedAt": "2026-11-03T01:02:32.997Z","error": null,"errorCode": null,"notice": "Public job postings from the company's official board API. Not affiliated with Greenhouse or Lever. Not for AI training."}]
Field notes:
remoteistruewhen the board marks the job remote (workplace type, or "Remote" in the location),falsefor hybrid or on-site, andnullwhen the board doesn't say.company: Lever's public API doesn't return a display name, so Lever items carry the board's company slug (spotify).updatedAtisnullfor Lever, which doesn't publish an update time.- Greenhouse has no separate apply link:
applyUrlis the posting page, which carries the application form. descriptionappears only withincludeDescription.- Closed items carry only
source,board,jobId,url(the board's posting URL rebuilt from the ID:https://job-boards.greenhouse.io/{token}/jobs/{id}orhttps://jobs.lever.co/{company}/{id}; it usually no longer opens, since the job is closed),changeType,firstSeenAt,lastSeenAt(last run the job was still on the board),checkedAtandnotice. Every other field (title,company,location,applyUrland so on) isnull, because the saved state keeps no job text.
The run also saves OUTPUT_SUMMARY in the default key-value store: per-board results (including baselined, notDeliveredCap and notDeliveredBudget), jobsFound, jobsMatched, new, existing, closed, failedBoards, chargedEvent, chargedJobs, budgetLimitReached, stateKey, the politeness settings used, the notice and finishedAt.
Change detection
The Actor keeps the state of each board in the named key-value store ats-jobs-feed-state, under the state key. The default key is derived from the boards and filters. Set your own stateKey to keep one feed stable across schedule or input edits. Under Apify's limited Actor permissions, if a pre-existing store with that name can't be opened, the Actor falls back to ats-jobs-feed-state-lp; if the run's token doesn't allow named stores at all, the run still completes as a first run (statePersisted: false and stateStoreError in OUTPUT_SUMMARY).
What the state holds: per job, only its ID, its status (open, or closed while its closed item is still waiting to be delivered) and two timestamps (firstSeenAt, lastSeenAt). No titles, descriptions, locations, departments or other job text. About 150 bytes per job, so a 1,000-job board takes about 150 KB.
- First run for a board (new state key, or a board added to the input): every job that matches the filters is recorded. Up to
maxJobsPerBoardare delivered asnew, and the rest, including any held back by your max charge, are recorded silently (not output, not charged). In the default mode they're delivered asexistingon later runs, and withchangesOnlythey're never sold to you asnew. - Later runs: a job in the state is
existing. A job that isn't in the state isnew. A job in the state that is no longer on the board at all isclosed, reported once, then dropped from the state. - Cap with
changesOnly:maxJobsPerBoardlimits the changes delivered per board per run, closed first, then new. Changes over the cap, or past your max charge, are kept pending and delivered (and charged) on a later run, never lost and never charged twice: a pending new job stays out of the state until it's delivered, and a pending closed job stays in it with statusclosed. If a pending closed job comes back on the board, it's simply open again. - Cap in the default mode: unchanged.
maxJobsPerBoardlimits the open jobs delivered, and closed items (free) all go out. A truly new job over the cap isn't recorded, so it's stillnewwhen it first gets delivered. - If a board can't be read in a run, its state is kept unchanged, so its jobs aren't reported as closed.
- State saved by 0.1.0/0.1.1 (which also stored titles, URLs and locations) is migrated on the next run: the extra fields are dropped and the record is rewritten. Because the old state only held jobs it had delivered, the first run after the upgrade records the board's other jobs silently instead of selling them as
new. Closed jobs are still reported.
Errors
A board that can't be read gives one item with error and errorCode, and is never charged:
board-not-found: the board token or company doesn't exist (HTTP 404).robots-disallowed: the API host's robots.txt disallows the request, or robots.txt couldn't be fetched (RFC 9309 treats that as "disallow all").fetch-failed: network error, server error, a response that isn't JSON, or the host was rate-limited.invalid-input: not a Greenhouse or Lever board (Ashby and other systems included), orboardswas left empty.
Pricing
$1.50 per 1,000 jobs. In change-tracking mode (changesOnly), only new and closed postings are charged. In the default mode you pay for each open job delivered (new or existing), and closed-job items are free. Error items are always free. The Actor stops delivering jobs when your run's max charge is reached, and says so in OUTPUT_SUMMARY.
Compliance and data use
- Not affiliated with Greenhouse, Lever or any company whose board you read, and not an integration partner of either.
- Not for AI training. The data is the companies' public job postings, delivered for job search, hiring-market research and monitoring. Don't use it to train AI models. Lever's job-board site declares
Content-Signal: ai-train=noin its robots.txt (jobs.lever.co/robots.txt). - No logins, no API keys, read-only. Only public GET endpoints are called.
- Polite: about 1 request per second per API host. The robots.txt of each API host is read first (RFC 9309: a server error or unreachable robots.txt means "disallow", a 4xx means "allow"), and its
Crawl-delayis honoured, up to 30 seconds (Lever's robots.txt sets 1 second).Retry-Afteris honoured, up to 30 seconds, and a host that answers HTTP 429 three times in a row gets no more requests in that run. User-Agent:ATSJobsFeed/0.1 (+Apify Actor; polite, robots-aware). - Personal data stripped: items are built from a fixed list of job fields, so recruiter, hiring-manager, owner and follower fields in the API data (Lever
owner,hiringManager,followers; Greenhouse metadata such as "Recruiter") are never output, and no person's name field is ever output. In descriptions, email addresses (including forms likename [at] company dot com) and phone numbers are replaced with[redacted]. Descriptions are off by default. Descriptions may contain names from the original posting: a name written into the description's prose (for example "created by …") is not detected, so leaveincludeDescriptionoff if you don't need it. - Every item and
OUTPUT_SUMMARYcarries the notice: "Public job postings from the company's official board API. Not affiliated with Greenhouse or Lever. Not for AI training."
Limits
- Greenhouse and Lever only. Ashby is not supported.
- Each board is one API request (plus one robots.txt request per host per run). Boards on the same host are read one after another, about 1 second apart, so 100 Lever boards take about 2 minutes.
- Memory: default 256 MB. In local tests, the largest board tried (Stripe, 740 jobs, with descriptions) peaked at 180 MB.
- Descriptions are plain text, cut at 5,000 characters.
Local test runs (2 Nov 2026)
With the prefill input (greenhouse:airbnb and lever:spotify, maxJobsPerBoard 20), the first run delivered 40 new jobs and the second delivered the same 40 as existing. Each run took about 2 seconds inside the Actor, and memory peaked at 124 MB.
Change tracking with a small cap (0.1.2): changesOnly with maxJobsPerBoard 10 on the same two boards and a fresh state key. The first run delivered 20 new jobs (10 per board) and recorded the other 276 silently (Airbnb 211 jobs, Spotify 85). The second run delivered 0 items and charged nothing. The saved state for both boards (296 jobs: IDs, status and timestamps only) is 48 KB. Each run took 2 to 5 seconds inside the Actor, and memory peaked at 134 MB.