ATS Jobs Feed: Greenhouse & Lever Job Postings avatar

ATS Jobs Feed: Greenhouse & Lever Job Postings

Pricing

from $1.50 / 1,000 job delivereds

Go to Apify Store
ATS Jobs Feed: Greenhouse & Lever Job Postings

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

William Schultz

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

20 hours ago

Last modified

Categories

Security

Limited permissions

Share

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 changesOnly on, maxJobsPerBoard caps the number of changes per run, not the board. The first run records every job on the board, delivers up to the cap as new and 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}, or api.eu.lever.co for 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

FieldDefaultWhat it does
boards (required)prefill greenhouse:airbnb, lever:spotifyBoard 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.
maxJobsPerBoard500 (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.
keywordsnoneKeep jobs whose title, department or team contains any of these words (case-insensitive).
locationsnoneKeep jobs whose location contains any of these (case-insensitive).
remoteOnlyfalseKeep only jobs the board marks as remote.
includeDescriptionfalseAdd 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.
changesOnlyfalseOutput only jobs that are new or closed since the previous run with the same state key.
stateKeyderived from boards and filtersName of the saved state used for change detection.
leverEufalseUse 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:

  • remote is true when the board marks the job remote (workplace type, or "Remote" in the location), false for hybrid or on-site, and null when 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).
  • updatedAt is null for Lever, which doesn't publish an update time.
  • Greenhouse has no separate apply link: applyUrl is the posting page, which carries the application form.
  • description appears only with includeDescription.
  • 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} or https://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), checkedAt and notice. Every other field (title, company, location, applyUrl and so on) is null, 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 maxJobsPerBoard are delivered as new, 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 as existing on later runs, and with changesOnly they're never sold to you as new.
  • Later runs: a job in the state is existing. A job that isn't in the state is new. A job in the state that is no longer on the board at all is closed, reported once, then dropped from the state.
  • Cap with changesOnly: maxJobsPerBoard limits 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 status closed. If a pending closed job comes back on the board, it's simply open again.
  • Cap in the default mode: unchanged. maxJobsPerBoard limits the open jobs delivered, and closed items (free) all go out. A truly new job over the cap isn't recorded, so it's still new when 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), or boards was 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=no in 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-delay is honoured, up to 30 seconds (Lever's robots.txt sets 1 second). Retry-After is 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 like name [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 leave includeDescription off if you don't need it.
  • Every item and OUTPUT_SUMMARY carries 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.