ATS Jobs Hiring Signals: Greenhouse, Lever & Ashby API avatar

ATS Jobs Hiring Signals: Greenhouse, Lever & Ashby API

Pricing

from $1.00 / 1,000 job returneds

Go to Apify Store
ATS Jobs Hiring Signals: Greenhouse, Lever & Ashby API

ATS Jobs Hiring Signals: Greenhouse, Lever & Ashby API

ATS jobs from Greenhouse, Lever and Ashby public APIs: salaries, remote flag, hiring signals and new-job alerts as clean JSON. No login.

Pricing

from $1.00 / 1,000 job returneds

Rating

0.0

(0)

Developer

Derek VanLaningham

Derek VanLaningham

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

12 days ago

Last modified

Share

Get every open job from any company on Greenhouse, Lever or Ashby: clean JSON, salaries, hiring signals, no login.

Turn a list of companies into every open job they're hiring for, in one run. This Actor reads the official public job-board APIs of Greenhouse, Lever and Ashby, so it's fast, stable and needs no login or proxies. Paste board URLs or just slugs (stripe, ramp) and get clean, typed JSON: title, department, locations, remote/hybrid, salary range (normalized to annual), posting date and apply link. Add a per-company hiring-signal summary (open roles, new in the last 7/30 days, top departments) or turn on monitor mode to get only new and removed jobs on a schedule.

Ready-to-run examples

Each example below is a published task: open its page and click Try to copy it into your account and run it, or paste the JSON into the Input tab (JSON view). Times, rows and costs are from real runs (FREE-tier prices; hiring summaries Oct 3 on 1.0.10, Figma and remote data jobs Oct 5 on 1.0.14); results change as the source data changes.

Figma Engineering Jobs with Salary Ranges (Greenhouse API)

Open this example: 3 s, 11 rows, $0.0185. All 11 rows had a salary range.

{"companies":["figma"],"ats":["greenhouse"],"keywords":["engineer"],"descriptionFormat":"none","maxTotalJobs":11}

Output excerpt (one row, selected fields):

{
"title": "Data Platform Engineer",
"companyName": "Figma",
"department": "Engineering",
"locations": [
"San Francisco, CA • New York, NY • United States",
"Canada",
"US"
],
"salary": {
"min": 153000,
"max": 376000,
"currency": "USD",
"interval": "year",
"minAnnual": 153000,
"maxAnnual": 376000,
"summary": "Annual Base Salary Range:",
"source": "greenhouse"
},
"postedAt": "2026-09-21T16:13:46.000Z",
"applyUrl": "https://boards.greenhouse.io/figma/jobs/6201407004?gh_jid=6201407004"
}

Hiring Signals: Stripe (Greenhouse), Palantir (Lever), Ramp

Open this example: 3 s, 3 rows, $0.017.

{"companies":["https://boards.greenhouse.io/stripe","https://jobs.lever.co/palantir","https://jobs.ashbyhq.com/ramp"],"outputMode":"summary_only"}

Output excerpt (one row, selected fields):

{
"recordType": "company_summary",
"companyName": "Ramp",
"ats": "ashby",
"openJobs": 157,
"newJobsLast30Days": 54,
"remoteShare": 0.09,
"salaryDisclosedShare": 0.96,
"medianSalaryMinAnnualUSD": 168000,
"topDepartments": [
{
"name": "Sales",
"count": 62
},
{
"name": "Engineering",
"count": 38
},
{
"name": "People & Talent",
"count": 13
},
{
"name": "Design",
"count": 7
},
{
"name": "Product",
"count": 7
}
]
}

Remote Data Jobs at Stripe, Ramp, Figma, Notion & Databricks

Open this example: 5 s, 11 rows, $0.0185. Uses "remoteMatch": "eligible": jobs where remote is at least one listed option (remoteEligible). The default strict match returned 2 rows.

{"companies":["stripe","ramp","figma","notion","databricks","plaid"],"keywords":["data"],"remoteOnly":true,"remoteMatch":"eligible","descriptionFormat":"none","maxTotalJobs":11}

Output excerpt (one row, selected fields):

{
"title": "Specialist Solutions Architect, Data",
"companyName": "Stripe",
"ats": "greenhouse",
"primaryLocation": "SF, NY, Remote",
"workplaceType": null,
"remoteEligible": true,
"postedAt": "2026-08-27T00:33:39.000Z",
"applyUrl": "https://stripe.com/jobs/search?gh_jid=8144262"
}

What you get

One dataset row per job with: title, companyName, department, team, employmentType, workplaceType (remote / hybrid / onsite), locations, country, salary (min, max, currency, interval, annualized min/max), postedAt, updatedAt, jobUrl, applyUrl, description (text / markdown / html / none) and stable IDs (jobKey).

{
"recordType": "job",
"jobKey": "ashby:ramp:34413f8d-26bf-4bbc-8ade-eb309a0e2245",
"ats": "ashby",
"boardSlug": "ramp",
"companyName": "Ramp",
"companyNameSource": "slug",
"atsJobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
"title": "Security Engineer, Cloud",
"department": "Engineering",
"team": "Backend",
"employmentType": "full_time",
"workplaceType": "hybrid",
"locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)"],
"primaryLocation": "New York, NY (HQ)",
"country": "USA",
"salary": { "min": 211400, "max": 290600, "currency": "USD", "interval": "year", "minAnnual": 211400, "maxAnnual": 290600, "summary": "$211.4K – $290.6K • Offers Equity", "source": "ashby" },
"postedAt": "2026-04-07T17:12:35.753Z",
"updatedAt": null,
"jobUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
"applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
"description": "About Ramp ...",
"descriptionTruncated": false,
"language": null,
"sourceApiUrl": "https://api.ashbyhq.com/posting-api/job-board/ramp?includeCompensation=true",
"scrapedAt": "2026-10-03T15:30:00.000Z",
"isNew": null
}

Why this Actor

  • Official public APIs: the same JSON endpoints companies use to embed their job boards. Stable, ToS-friendly, no bot-fighting, no login, no proxy needed.
  • Three ATSs in one call: Greenhouse, Lever (incl. EU) and Ashby, normalized into one schema.
  • Auto-detects the slug: pass stripe and it checks all three ATSs in parallel (keeps every board that exists).
  • Salaries normalized: min/max, currency, interval, plus minAnnual/maxAnnual (never currency-converted).
  • Hiring signals: one summary row per company (open jobs, new in 7/30 days, remote share, top departments/locations, salary disclosure, median USD salary).
  • Monitor mode: only new jobs since the last run, plus job_removed rows for jobs that disappeared.
  • Cheap per-job pricing, and MCP-ready for AI agents (stable camelCase keys, every key always present, null when unknown).

Use cases

  • Sales / GTM: hiring = buying intent. "Which of my 500 target accounts are hiring data engineers this month?"
  • Recruiting / sourcing: track openings at target companies.
  • Job boards and aggregators: bulk, normalized feeds.
  • Market and comp research: salary ranges, remote share, department mix.
  • AI agents: "Is X hiring for Y?" in one tool call.

How to use the Greenhouse, Lever and Ashby jobs API

  1. Add companies. Paste job-board URLs (https://boards.greenhouse.io/stripe, https://jobs.lever.co/palantir, https://jobs.ashbyhq.com/ramp) or just slugs (stripe, ramp) into Companies. Slugs are checked on Greenhouse, Lever and Ashby automatically (limit this with ATS).
  2. Filter (optional). Use Keywords (e.g. engineer, data), Departments, Location contains, Employment types, Remote only and Posted within days.
  3. Pick the output. jobs returns one row per job, with salary ranges converted to annual figures where the company publishes pay. summary_only returns one hiring-signal row per company (open jobs, new jobs in the last 7 and 30 days, remote share, top departments and locations, salary disclosure). jobs_and_summary returns both.
  4. Run it. One request per job board, so most runs finish in seconds. Example: 1,000 SpaceX jobs in 5.4 s.
  5. Monitor (optional). Turn on Only new jobs with a Monitor ID and schedule the Actor daily to get only jobs you haven't seen.
  6. Use it from AI agents. The Actor works as a tool through Apify's MCP server (mcp.apify.com) for Claude, Cursor and other MCP clients.

You pay per result: $1.50 per 1,000 jobs on the Free plan (less on paid plans), plus $0.002 per run start. Data comes only from the companies' public job-board APIs. No login or proxy is needed.

Example inputs

Single company

{ "companies": ["https://boards.greenhouse.io/stripe"] }

Bulk list, small payload

{ "companies": ["stripe", "ramp", "palantir", "figma", "notion"], "descriptionFormat": "none", "outputMode": "jobs_and_summary" }

Filtered remote engineering

{ "companies": ["ramp", "figma"], "keywords": ["engineer"], "excludeKeywords": ["intern"], "remoteOnly": true, "postedWithinDays": 30 }

Monitor mode (schedule daily)

{ "companies": ["stripe", "ramp", "palantir"], "onlyNewJobs": true, "monitorId": "my-accounts", "descriptionFormat": "none" }

Create a schedule for this input. The first run outputs every job as new (that's expected); later runs output only new jobs and job_removed rows.

Input reference

FieldTypeDefaultDescription
companiesstring[] (required)–ATS board URLs, ATS API URLs or bare slugs. Max 1000.
atsstring[]all threeWhich ATSs to try for bare slugs (greenhouse, lever, ashby). URLs always use their own ATS. For a bare slug, Lever is tried on api.lever.co first and then on the EU host api.eu.lever.co. The EU fallback is only tried for bare slugs. A jobs.lever.co/... URL is looked up only on the global host, so paste the jobs.eu.lever.co/... URL for an EU board.
outputModejobs | jobs_and_summary | summary_onlyjobsJob rows, job rows + one summary per company, or summaries only. Summary stats cover the whole board, except matchingJobs, which counts only the jobs that pass your filters.
keywordsstring[][]Keep jobs whose title contains any of these (case-insensitive). With keywordScope: "title_and_description", the description is searched too.
keywordScopetitle | title_and_descriptiontitleWhere keywords match. title_and_description searches the full job description as well, even with descriptionFormat: "none" (no extra cost). excludeKeywords always match the title only.
excludeKeywordsstring[][]Drop jobs whose title contains any of these.
locationContainsstring[][]Keep jobs where any location contains one of these.
departmentsstring[][]Keep jobs whose department or team contains any of these (case-insensitive), e.g. Engineering. Jobs with neither field are dropped when set.
employmentTypesstring[][]Keep only these normalized employmentType values: full_time, part_time, contract, intern, temporary, other. Jobs with an unknown type are kept too (see next row). Listing unknown always keeps them.
includeUnknownEmploymentTypebooleantrueOnly used with employmentTypes. true also keeps jobs whose ATS publishes no employment type (employmentType: null, e.g. most Greenhouse boards). false drops them, unless unknown is listed.
remoteOnlybooleanfalseKeep only remote jobs (see remoteMatch).
remoteMatchstrict | eligiblestrictWith remoteOnly: strict keeps workplaceType == "remote"; eligible keeps remoteEligible == true.
postedWithinDaysinteger0Only jobs first published within N days (0 = no limit; undated jobs are kept only when 0).
descriptionFormatnone | text | markdown | htmltextDescription format. none is smallest.
maxDescriptionCharsinteger0Truncate descriptions (sets descriptionTruncated).
maxJobsPerCompanyinteger0Cap job rows per company after filters. In monitor mode, new jobs cut off by this cap stay "new" and are output by the next run.
maxTotalJobsinteger0Global cap on job rows (cost control). In monitor mode, new jobs cut off by this cap stay "new" and are output by the next run.
onlyNewJobsbooleanfalseMonitor mode: output only jobs not seen in earlier runs with the same monitorId, plus free job_removed rows. With outputMode: "summary_only", the seen-jobs list is not updated.
monitorIdstringdefaultMonitor state name (letters, digits, -).
proxyConfigurationproxyoffNot needed for these official APIs.

Output reference

Every row has recordType. Filter on it first. All keys of a record type are always present.

job

FieldTypeNotes
jobKeystring{ats}:{boardSlug}:{atsJobId}, globally unique
atsgreenhouse|lever|ashby
boardSlugstringlower-case
companyName / companyNameSourcestring|null / ats|slugGreenhouse provides the name; Lever/Ashby use the title-cased slug
atsJobId, titlestring
department, teamstring|null
employmentTypefull_time|part_time|contract|intern|temporary|other|nullGreenhouse: always null (not standardized)
workplaceTyperemote|hybrid|onsite|nullLever/Ashby: from the board's workplace field. Greenhouse has no such field: remote only when every location in the name says remote (US-Remote, Remote US); office lists that also mention remote (SF, NYC, Remote) and all other jobs are null
remoteEligiblebooleantrue when the workplace field says remote or any listed location mentions remote
locations / primaryLocationstring[] / string|null
countrystring|nullLever ISO-2, Ashby as given, Greenhouse null
salaryobject|null{min, max, currency, interval, minAnnual, maxAnnual, summary, source}
postedAt / updatedAtISO 8601 UTC|null
jobUrl / applyUrlstring / string|null
description / descriptionTruncatedstring|null / booleanper descriptionFormat
languagestring|nullGreenhouse only
sourceApiUrl, scrapedAtstring
isNewboolean|nulltrue in monitor mode, else null

company_summary: ats, boardSlug, companyName, boardUrl, openJobs (all jobs on the board), matchingJobs (after your filters), newJobsLast7Days, newJobsLast30Days, remoteShare (0–1; Lever/Ashby: over jobs with a known workplace type, null if none; Greenhouse: clearly-remote jobs ÷ all jobs, since Greenhouse has no workplace field), topDepartments, topLocations (top 5 {name, count}), salaryDisclosedShare (0–1), medianSalaryMinAnnualUSD, scrapedAt. Stats other than matchingJobs are computed over all open jobs on the board.

error (free): input, ats, boardSlug, errorCode (BOARD_NOT_FOUND, NOT_DETECTED, INVALID_INPUT, UPSTREAM_ERROR, RATE_LIMITED), message, scrapedAt. One row per company entry that returned nothing, so you (or your agent) can see why.

job_removed (free, monitor mode): jobKey, ats, boardSlug, title, jobUrl, lastSeenAt, scrapedAt.

Remote: strict vs eligible. workplaceType: "remote" is strict (the source says the job is remote, and on Greenhouse every listed location says remote); remoteEligible: true is loose (remote is at least one option, e.g. SF, NYC, Seattle, Remote). Stripe, Oct 3: 35 strict vs 114 eligible of 715.

Salary notes: Ashby uses the first Salary component of compensation.compensationTiers. Lever uses salaryRange. Greenhouse uses the first of pay_input_ranges[] (min_cents/max_cents ÷ 100, currency_type, title as summary). Greenhouse has no interval field. interval is hour when the range title (or, failing that, the blurb) mentions an hour ("hour", "hourly", "per hour", "/hr"). It is year when it mentions a year ("year", "yearly", "annual", "per annum"). Otherwise it is null, and then minAnnual/maxAnnual are null too. Annualization: year ×1, month ×12, week ×52, day ×260, hour ×2080. Example from Figma: title "Annual Base Salary Range:" with min_cents 16500000 gives {min: 165000, max: 190000, currency: "USD", interval: "year", minAnnual: 165000, maxAnnual: 190000}. Greenhouse only returns pay ranges when the request includes pay_transparency=true. The Actor always sends it on the same single request (no extra calls). Currencies are never converted.

Pricing

Pay per event: you only pay for what you get. No platform usage charges.

EventPrice (Free tier)When
job$0.0015per job row
company-summary$0.005per company summary row
company-checked$0.0005per board checked, monitor mode only
apify-actor-start$0.002once per run (charged by Apify)

Higher Apify plans get lower per-job prices (down to $0.0010 per job). error and job_removed rows are free.

Worked examples: 1,000 jobs ≈ $1.50 on the Free tier. Checking 100 companies daily in monitor mode ≈ $0.05/day + $0.0015 per new job. "Is X hiring?" for 50 companies with summary_only ≈ $0.25.

Set Max cost per run to cap spending. The Actor stops cleanly when it's reached ("Stopped: your max-cost-per-run limit was reached after N jobs") and you keep everything delivered so far.

Use as an MCP tool / with AI agents

Add it to any MCP client (Claude, Cursor, …) via the Apify MCP server:

https://mcp.apify.com/?tools=OptimistPrime/ats-jobs-hiring-signals

Example tool call:

{ "companies": ["ramp"], "descriptionFormat": "none" }

Tips for agents:

  • Use descriptionFormat: "none" (or maxDescriptionChars: 1000) for large lists to keep payloads small.
  • Use outputMode: "summary_only" for "is company X hiring?" questions (one row per company).
  • Use maxTotalJobs to bound cost and response size.
  • Check recordType: "error" rows to learn why a company returned nothing (e.g. not on these ATSs).

Integrations

API (JavaScript)

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('OptimistPrime/ats-jobs-hiring-signals').call({ companies: ['stripe', 'ramp'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();

API (Python)

from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("OptimistPrime/ats-jobs-hiring-signals").call(run_input={"companies": ["stripe", "ramp"]})
items = client.dataset(run["defaultDatasetId"]).list_items().items
  • Schedules + webhooks: run monitor mode daily and POST new jobs to your CRM or Slack.
  • Make, Zapier, n8n: use the Apify app/node, "Run Actor" + "Get dataset items".
  • Google Sheets: export the overview view as CSV, or use the Google Sheets integration.

Limits and FAQ

  • How is this different from other Greenhouse, Lever or Ashby scrapers on Apify? Other Greenhouse/Lever/Ashby Actors on the Store cost between $0.0015 and $0.004 per job and are reliable. Choose this one if you want all three ATSs in one run, normalized salaries, per-company hiring-signal summaries, or new-jobs-only monitoring.
  • Which companies are covered? Only companies whose job board is hosted on Greenhouse, Lever or Ashby. Companies on Workday, iCIMS, SmartRecruiters, Workable etc. are not covered in v1.
  • How do I find a company's slug? Open their careers page and click a job. If the URL is boards.greenhouse.io/<slug>, job-boards.greenhouse.io/<slug>, jobs.lever.co/<slug> or jobs.ashbyhq.com/<slug>, paste that URL. Company career-site URLs (e.g. stripe.com/jobs) return a NOT_DETECTED row in v1.
  • Do summary stats respect my filters? Only matchingJobs does. It counts the jobs that pass keywords (with keywordScope), excludeKeywords, locationContains, departments, employmentTypes, remoteOnly and postedWithinDays. openJobs, newJobsLast7Days/newJobsLast30Days, remoteShare, topDepartments, topLocations, salaryDisclosedShare and medianSalaryMinAnnualUSD describe the whole board.
  • Am I charged for jobs my filters drop? No. The job event is charged only for job rows that reach your dataset after all filters and caps. Filtered-out jobs are free.
  • employmentTypes and Greenhouse: Lever and Ashby publish an employment type for each job. Greenhouse has no standard field; it is filled only when a board publishes it as custom metadata (e.g. "Time Type: Full Time"), otherwise employmentType is null. By default these unknown-type jobs are kept when you filter by employmentTypes, so Greenhouse boards don't silently disappear. Set includeUnknownEmploymentType: false for a strict filter.
  • Does excludeKeywords search descriptions? No, it always matches the title only, also with keywordScope: "title_and_description".
  • departments matching: a case-insensitive substring of department or team, so Engineering also matches "Platform Engineering". Greenhouse boards sometimes prefix departments with cost-centre numbers (e.g. "7112 Data Science"); substring matching handles that. Greenhouse only returns departments and offices together with job content, so the Actor always requests it (still one request per board, no extra charge). With descriptionFormat: "none" the description HTML is dropped right after download and never output.
  • Keywords in descriptions: with keywordScope: "title_and_description", a job matches if any keyword is in the title or the full description text. Matching uses the full text even when the output description is none, HTML or truncated. On Greenhouse this fetches the job content in the same single request, so runs move a little more data but cost the same.
  • Monitor mode + maxJobsPerCompany/maxTotalJobs: only jobs that were actually delivered (and charged) are marked as seen. New jobs that a cap cut off stay "new", so the next run outputs them. Nothing is skipped, and nothing is charged twice.
  • Monitor mode + summary_only: a summaries-only run delivers no job rows, so it doesn't touch the seen-jobs list. It marks nothing as seen and emits no job_removed rows. To advance monitoring, run a full jobs pass (outputMode jobs or jobs_and_summary) with the same monitorId.
  • EU Lever boards: the EU host (api.eu.lever.co) is only tried as a fallback for bare slugs. For a Lever URL, the URL's own host is used. Paste https://jobs.eu.lever.co/<slug> for EU boards.
  • First monitor run outputs everything. That's how monitoring starts; later runs only output changes. Jobs are only marked removed for boards fetched successfully in that run.
  • Data freshness: live; each run reads the boards at run time.
  • Rate limits: requests are throttled to 2/s per API host with retries; you shouldn't need a proxy.

This Actor reads public job postings from official job-board APIs that companies publish for embedding. It collects no personal data and uses no login. You are responsible for how you use the data downstream. Not affiliated with, or endorsed by, Greenhouse, Lever or Ashby.

Support / changelog

Open an issue in the Issues tab; we aim to reply within 24 hours. See ./CHANGELOG.md.