ATS Job Board API — Greenhouse & Lever Jobs for AI Agents avatar

ATS Job Board API — Greenhouse & Lever Jobs for AI Agents

Pricing

from $3.00 / 1,000 jobs

Go to Apify Store
ATS Job Board API — Greenhouse & Lever Jobs for AI Agents

ATS Job Board API — Greenhouse & Lever Jobs for AI Agents

Get current job postings from the official public Greenhouse and Lever job-board APIs. Pass company board tokens or careers URLs; get normalized JSON with title, location, remote, department, dates and original posting link. Built for AI agents and Apify MCP. $3 per 1,000 jobs.

Pricing

from $3.00 / 1,000 jobs

Rating

0.0

(0)

Developer

AgentReady Tools

AgentReady Tools

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

21 hours ago

Last modified

Features

Limited permissions

Share

Fetch currently published jobs directly from official public Greenhouse and Lever APIs. One predictable JSON shape makes it easy to build job alerts, recruiting research, and agent tools without integrating two different ATS formats. No source API key is required.

Disclaimer

  • Unofficial tool. Not affiliated with, endorsed by, or sponsored by Greenhouse Software, Inc. or Lever, Inc. "Greenhouse" and "Lever" are trademarks of their respective owners, used only to describe compatibility.
  • Uses only public, unauthenticated GET endpoints (boards-api.greenhouse.io/v1/boards/{token}/jobs and /v1/boards/{token}; api.lever.co/v0/postings/{site} and api.eu.lever.co) plus each host's robots.txt. No login, no API keys, no access controls bypassed, no application (POST) endpoints.
  • Polite by design: max 1 request per second per host (honours Lever's robots.txt Crawl-delay: 1), respects robots.txt and HTTP 429/Retry-After, no proxy rotation to evade blocking.
  • Only jobs the employer has published publicly. Each result links to the original posting (url); the original posting is authoritative.
  • Personal data: emails and common phone-number formats found in job text are removed by default. You are responsible for your own use of the output, including compliance with GDPR and other data-protection, copyright, database-right and anti-spam laws, and with the source sites' terms. Do not use output for profiling individuals or unsolicited messaging.
  • No AI training: Lever's job site declares ai-train=no (an opt-out under Art. 4 of Directive (EU) 2019/790). Do not use Lever-sourced output to train or fine-tune AI models.
  • Job descriptions remain the property of the publishing employers. Use metadata-only mode (the default, includeDescription=false) if you don't need full text.
  • On demand: the Actor fetches only the companies you list (or the documented defaults for an omitted/empty list), at run time. It does not build or keep a cumulative jobs database.
  • No warranty: data is provided "as is", may be incomplete or outdated, and may change if the source APIs change.
  • Removal requests: employers or individuals can request exclusion of a board or data via this Actor's Issues tab; we honour verified requests promptly by maintaining the local denylist described below.

For AI agents

Apify MCP: https://mcp.apify.com?tools=samgov-tools/ats-jobs

Example prompt: “Use samgov-tools/ats-jobs to find up to 20 remote engineering jobs at greenhouse:discord and lever:leverdemo. Include posting links; omit descriptions.”

Start with {} for a small default run (up to 10 jobs), or provide companies and maxItems. Use explicit provider prefixes when known. A bare slug tries Greenhouse first and tries Lever only after a Greenhouse 404. Lever global 404 falls back once to its EU API; an EU board URL starts directly in the EU.

To find a token, follow the company's careers link to its hosted board: job-boards.greenhouse.io/discord gives greenhouse:discord; jobs.lever.co/leverdemo gives lever:leverdemo. The token is the first path segment, not the job identifier. Embedded careers pages may link to these boards; this Actor does not discover tokens or scrape careers pages.

Use metadata-only mode (default): includeDescription=false, with descriptionText always null. Greenhouse requests omit content=true unless descriptions, department details, or keyword matching in descriptions require it. Filtering can fetch content while keeping output descriptionText null. Keywords search titles and descriptions. Prefer a location or department filter for precision. Use maxItemsPerCompany to keep the first company from filling the entire run.

Read OUTPUT for company errors and the stop reason. Dataset items contain only jobs. No records are emitted for failures. Results follow provider order, not date order.

Paying agents without an Apify account (x402 / MPP)

An agent without an Apify account can buy a prepaid Apify API token from Apify AGI (agi.apify.com) with x402 (USDC on Base or Solana), MPP (Tempo or Solana) or Skyfire, then use it as the Apify API token in this README's REST example (Actor samgov-tools~ats-jobs) or as the bearer token for the Apify MCP server. This Actor uses limited permissions, which prepaid tokens require. Apify's current terms: minimum $1, the balance is a hard spending cap, the token expires 14 days after purchase, unused balance is non-refundable, and the feature is experimental. Check agi.apify.com for current terms before paying.

The token pays this Actor's normal pay-per-event prices: job: $0.003 per stored job, company-board: $0.001 per fetched board, apify-actor-start: $0.00005 per run. See Let your AI agent pay for Actor runs, x402 and MPP.

Quick start

{
"companies": ["greenhouse:discord", "lever:leverdemo"],
"keywords": "engineer, data",
"remoteOnly": true,
"maxItems": 20,
"maxItemsPerCompany": 10
}

REST synchronous run and dataset items (requires an Apify API token and a published Actor):

curl --request POST \
--url 'https://api.apify.com/v2/acts/samgov-tools~ats-jobs/run-sync-get-dataset-items' \
--header "Authorization: Bearer $APIFY_TOKEN" \
--header 'Content-Type: application/json' \
--data '{"companies":["greenhouse:discord"],"maxItems":10}'

JavaScript (npm install apify-client):

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('samgov-tools/ats-jobs').call({
companies: ['greenhouse:discord', 'lever:leverdemo'],
maxItems: 20,
maxItemsPerCompany: 10,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const summary = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('OUTPUT');
console.log(items, summary?.value);

Input

FieldDefaultMeaning
companies["greenhouse:discord","lever:leverdemo"]Up to 50 strings. Provider prefixes, supported board URLs, or bare slugs. Missing/empty list uses defaults.
keywordsnoneComma-separated ANY match, case-insensitive, in title or fetched description. Example: engineer, data.
locationnoneSubstring across primary/all locations and Lever country code. Example: London.
remoteOnlyfalseLever remote classification or “remote” in location/office text.
departmentnoneDepartment substring. Enables Greenhouse content.
postedWithinDaysnoneInteger 1–365. Greenhouse first publication (fallback update), Lever creation time.
includeDescriptionfalseInclude plain descriptionText, up to 20,000 characters; otherwise null.
stripContactInfotrueRemove emails and common phone formats from descriptionText for GDPR data minimization. If false, you take responsibility for personal data. Titles are unchanged.
maxItems10Total stored jobs, integer 1–5000. Platform paid-item ceiling can lower this to zero.
maxItemsPerCompanynonePer-company stored-job cap, integer 1–5000.

Supported URL hosts: boards.greenhouse.io, job-boards.greenhouse.io, job-boards.eu.greenhouse.io, boards-api.greenhouse.io (the /v1/boards/<token> path), jobs.lever.co, and jobs.eu.lever.co. Job-specific URLs on these boards also work. URL query parameters are ignored. Tokens are trimmed, lowercased and deduplicated; invalid inputs and lists longer than 50 are rejected before network access.

Output

Illustrative dataset item, with includeDescription: true:

{
"source": "lever",
"company": "example",
"companyToken": "example",
"jobId": "abc-123",
"title": "Data Engineer",
"location": "London, UK",
"allLocations": ["London, UK", "Remote Europe"],
"remote": true,
"workplaceType": "remote",
"department": "Engineering",
"team": "Data Platform",
"employmentType": "Full-time",
"url": "<original posting URL>",
"applyUrl": "<original application URL>",
"postedAt": "2026-11-12T10:00:00.000Z",
"updatedAt": null,
"descriptionText": "Build reliable data systems.",
"fetchedAt": "2026-11-14T10:00:00.000Z"
}

Field order is fixed. descriptionText is always present: null unless requested, also null when empty. Contact stripping runs before the 20,000-character cap; common international/US formats are recognized, but this is not a guarantee of complete anonymization. Other unavailable optional fields are null, with an empty array for unavailable locations. Descriptions decode HTML entities, strip tags/scripts/styles and collapse whitespace. They are plain text, not sanitized HTML for rendering. URLs come from the API; missing original posting URLs cause a job to be skipped, counted as jobsSkippedMissingUrl per company and in totals. Missing titles become empty strings. No raw description or list HTML is included in other fields. jobId is the public posting ID, not Greenhouse internal_job_id.

Greenhouse employer names use company_name, then board metadata, then the token if metadata is unavailable. Lever uses the site slug. Greenhouse department/office details may be absent without content; team and employmentType are null. Greenhouse workplaceType is remote only when inferred from location/office text, otherwise null. Lever unspecified becomes null. Country participates in filtering but is not an extra output field. Lever updatedAt is always null.

Remote detection is a simple source/location signal, not a guarantee of work-from-anywhere eligibility. A hybrid classification alone does not pass remoteOnly. Missing dates fail the recency filter. Updated dates can make Greenhouse jobs look recent when first publication is unavailable.

OUTPUT summary

The default key-value store record OUTPUT is also linked in the Actor output schema:

{
"companies": [
{
"input": "greenhouse:discord",
"source": "greenhouse",
"token": "discord",
"status": "ok",
"jobsFound": 100,
"jobsStored": 10,
"jobsSkippedMissingUrl": 0,
"error": null
}
],
"totals": {
"companiesAttempted": 1,
"companiesOk": 1,
"companiesNotFound": 0,
"companiesError": 0,
"jobsFound": 100,
"jobsStored": 10,
"jobsSkippedMissingUrl": 0,
"bytesDownloaded": 50000,
"requests": 2,
"stopReason": "max_items"
}
}

Counts above are illustrative. jobsFound counts fetched postings before filters. jobsStored counts accepted job pushes. Statuses are ok, not_found, error, blocked_by_robots, robots_unavailable, rate_limited, and excluded. All non-ok statuses are free, with zero found/stored counts, and a safe error message; no failure dataset item is pushed. Source can be null for an unsuccessful bare-slug lookup. Summaries cover attempted distinct boards only; companies beyond a budget or total-item stop are not fetched. A fetched board remains ok even if budget prevents its charge or storage; stopReason explains the stop. Duplicate resolved provider/token boards are not charged twice. bytesDownloaded measures consumed successful-response body bytes after decompression, not HTTP wire bytes; requests includes robots.txt, retries and optional metadata requests. Policy statuses are listed per company; companiesError counts only generic error, not policy outcomes. Stop reasons are completed, max_items, or budget.

Pricing

Pay per event. The Pricing tab on the Actor page is authoritative.

EventCharged whenPrice
jobOne normalized job stored in the dataset, with its original posting link$0.003 ($3 per 1,000 jobs)
company-boardOne company board successfully fetched from the official Greenhouse or Lever API$0.001
apify-actor-startAutomatically by Apify once per run (per GB of memory)$0.00005

Example: 100 jobs from 2 companies in one run cost $0.30 + $0.002 + $0.00005 = $0.30205. The default empty-input run (10 jobs, usually 1 board) costs about $0.031.

A successful board is charged once even if it is empty or filters match no jobs. Unknown, excluded, robots-blocked, rate-limited or failed boards are free. All Lever pages must succeed before the board is charged. The Actor checks your run's maximum charge before every charge and stops cleanly when the budget is reached; ACTOR_MAX_PAID_DATASET_ITEMS also caps stored jobs. No source API credentials are used.

Data sources & terms

Only the official public Greenhouse Job Board API and Lever Postings API are used.

See the Disclaimer for ownership, reuse, personal-data responsibilities and Lever's AI training opt-out. Public API availability does not grant unrestricted reuse rights.

Verified removal requests are implemented in src/denylist.ts: maintain lowercase provider/token entries in DENYLIST (empty by default). Checks run before fetching or charging. Bare slugs are conservatively excluded if either provider/token is listed. This is a source-maintained exclusion list, not a persistent jobs database. The maintainer updates it after verifying requests submitted via the Issues tab.

Limits and operational behavior

  • At most 50 company entries and 5000 stored jobs per run. Company processing is sequential; the first company can consume the total cap.
  • All HTTP request starts are spaced at least 1000 ms apart per host, including robots.txt, retries, metadata, pagination and fallback. Hosts have independent limiters; a larger selected robots Crawl-delay increases the interval.
  • Before the first API request to each host, fetch its robots.txt with a 10-second timeout and cache its final result (including an exhausted failure) per host only for this run; later companies on that host reuse it without another fetch. Select ats-jobs-apify-actor user-agent groups, falling back to *. Longest Allow/Disallow match wins, Allow wins ties; * and terminal $ patterns are supported. Disallowed paths stop the company with blocked_by_robots. Robots 4xx except 429 allows all; 5xx, network failures, redirects, timeouts or unreadable responses fail closed with robots_unavailable after retries are exhausted. Robots uses the same retry policy as API requests, including 429 handling.
  • API requests have a 30-second timeout. Both API and robots requests have at most two retries for 429/5xx/network failures. Honour Retry-After seconds or HTTP-date; otherwise wait 2 s, then 4 s. Retry waits are capped at 60 s: a Retry-After over 60 s stops the company as rate_limited without waiting. The per-host limiter still applies to every retry. API 404s are not retried.
  • Requests use a named User-Agent, allow only the three API hosts, and reject redirects. Input URLs never become arbitrary outbound requests.
  • Responses are bounded at 25 MiB per request, using header and streamed-body checks. Lever uses pages of 100 until a short page, rejects repeated pages, and has a safety limit of 1000 pages per board.
  • A complete board is fetched before billing/filtering. maxItems limits stored jobs, not upstream board size. Large description boards use more memory and bandwidth. The default Actor memory is 512 MiB; choose more for unusually large boards.
  • Unknown boards, network failures and API errors are isolated per company. Storage/billing infrastructure failures fail the run instead of silently losing records. Optional Greenhouse metadata API errors use the token as company name; robots and excessive Retry-After policy failures stop the company without charge.
  • No sorting, application submissions, employer discovery, Ashby support or job-history tracking. Results are a snapshot, not a hiring-status guarantee.

Local development

Requires Node.js 22+; the Docker image targets Node 22. The SDK entry point is src/main.ts; src/run.ts is an injectable pipeline usable without Apify initialization. All code is clean-room project code using the documented public response shapes.

npm ci
npm run build
npm test
# Optional network-only smoke; does not initialize Apify or charge real events:
RUN_LIVE_TESTS=1 npm test
# Actual public API measurements with no-op billing/storage:
npm run measure
# Recreate the icon (Python 3 with Pillow) and schemas:
python3 scripts/make-icon.py
python3 scripts/make-schemas.py

Tests use node:test via tsx --test, mocked fetch and injected clocks/sleep. The live test is skipped unless explicitly enabled. Measurement cases: default input; five requested boards with descriptions and maxItems 5000; the same boards without descriptions and maxItems 10. Each prints wall time, sampled peak RSS, process lifetime maxRSS, downloaded bytes, and found/stored counts. Later cases share the process and memory allocator, so RSS is not isolated per case. The small case stops after reaching ten jobs and may not fetch all five boards.

FAQ

Why did I get no jobs? Check OUTPUT: the board may be empty, no filters matched, the token may be unknown, or the budget may have stopped storage. An unknown company does not fail other companies.

Why is the company a slug? Lever has no reliable employer display name in these responses. Greenhouse falls back to its token when neither postings nor metadata provide a name.

Why are some departments or remote jobs missing? Greenhouse lightweight responses omit departments/offices. Set a department filter or includeDescription: true to retrieve those fields; published location text can still be incomplete.

Can I apply to jobs with this Actor? It returns public job/application links. It never submits applications.

Can agents use the results? Yes, for job lookup and research subject to source rights and policies. Do not use the output for AI model training.