ATS Jobs API — Greenhouse, Lever, Ashby, Workday & more
Pricing
from $1.49 / 1,000 jobs
ATS Jobs API — Greenhouse, Lever, Ashby, Workday & more
Open jobs from company career boards on Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Recruitee, Personio, Teamtailor, BambooHR, Breezy, Pinpoint, Rippling, Oracle, UKG, ADP, SuccessFactors and Paylocity in one schema: salary, remote/hybrid, country, seniority, visa sponsorship.
Pricing
from $1.49 / 1,000 jobs
Rating
0.0
(0)
Developer
Attila Kis
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a month ago
Last modified
Categories
Security
Limited permissionsShare
Read public company job boards and return matching jobs in one schema. Supported platforms: Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Recruitee, Personio, Breezy HR, Pinpoint, Rippling, Teamtailor, BambooHR, Oracle Cloud HCM, UKG Pro (UltiPro), ADP Workforce Now, SAP SuccessFactors and Paylocity. Input can be a board URL, company name, domain, career page, CSV or Apify dataset. Search limits and source failures are reported with the results.
What you get for each job
- Title, company, company website, department, team, job page and apply link
- Locations, countries (ISO codes) and workplace type: remote, hybrid or on-site
- Remote eligibility: explicit allowed countries, regions, timezone requirements and exclusions, separate from stated office locations
- Employment type (full-time, part-time, contract, temporary, internship) and seniority (intern … executive)
- Salary: min, max, currency and period, from the ATS pay fields or from the pay statement in the job text, plus the yearly amount in US dollars (ECB daily rates)
- Years of experience the job asks for ("5+ years" → min 5) and skills and tools (Python, Kubernetes, Salesforce, … from a table of 131)
- Visa sponsorship: available / not available, when the job text says it
- Posting date (Workday: the exact date, not only "30+ days ago")
- Description as plain text or HTML, with personal emails and phone numbers removed
- Detail and description status, with warnings when a detail request fails or the output description is cut
Input examples
One company:
{ "companies": ["https://job-boards.greenhouse.io/stripe"] }
Several companies on different ATS platforms, remote engineering jobs that explicitly allow work from the US or Canada:
{"companies": ["https://job-boards.greenhouse.io/stripe","https://jobs.lever.co/palantir","https://jobs.ashbyhq.com/openai","https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"],"titleKeywords": ["engineer", "developer"],"remoteCountries": ["US", "CA"],"workplaceTypes": ["remote"]}
Search the first catalog page for data jobs posted within seven days:
{"searchAllCompanies": true,"titleKeywords": ["data engineer", "data scientist", "analytics engineer"],"postedWithinDays": 7,"descriptionFormat": "none"}
You can give a board URL (Workday also on myworkdaysite.com), a job URL on that board, platform:name, or a company name (Stripe) when it is in the built-in list. Company domains and career URLs are also accepted: the Actor looks for supported ATS links in the public page and follows up to two same-site career links. It reads links, iframes and common embeds without running JavaScript. A page that needs JavaScript may need a direct ATS URL. Unresolved pages get ats_not_found or discovery_error in COMPANIES.
EU Lever uses https://jobs.eu.lever.co/acme, https://api.eu.lever.co/v0/postings/acme, or lever:eu/acme. The EU and global boards have separate job keys.
CSV and dataset company lists
Add companyCsvUrl for a public HTTP(S) CSV file, or companyDatasetId for a dataset in your Apify account. The selected column or top-level field is companyInputField, which defaults to company. These rows are combined with companies. Empty values are skipped; values may be names, domains, career URLs or ATS references. The combined company input supports up to 20,000 entries. Dataset reads have a shared 10,000-row run limit, including blank rows, previous snapshots and restart output. Oversized datasets are rejected before their items are downloaded; split company lists or use smaller snapshots. Input datasets must remain unchanged during a run. CSV files need a header and must be at most 4 MB. URLs must be public, without login or custom ports.
{"companyDatasetId": "YOUR_COMPANY_DATASET_ID","companyInputField": "website","skills": ["Python", "Kubernetes"],"skillsMatch": "all","visaSponsorship": "available"}
CSV example for the default column:
companyacme.example.comhttps://jobs.eu.lever.co/acme
Only new jobs (scheduled runs)
previousDatasetId compares job IDs with the dataset you provide. Known jobs are not written or charged; jobs missing from a fully read board go to CLOSED_JOBS in the current run's default key-value store. This is a comparison with one dataset, not a complete historical baseline. Do not use a delta-only output dataset as the next baseline: it omits unchanged jobs. This mode does not detect updated job content.
Optional new and updated job tracking
Change tracking is off by default. To keep a complete baseline across scheduled runs, turn on changeTracking and set stateStoreName, or select an existing store with stateStoreId. The Actor reads and writes JOB_STATE in a named key-value store in your own Apify account. It does not use a developer-owned job database. You control and can delete this store.
{"companies": ["https://job-boards.greenhouse.io/stripe"],"changeTracking": true,"stateStoreName": "my-job-monitor"}
The first run emits matching jobs as changeStatus: new. Later runs emit new, updated or reopened matching jobs; unchanged jobs stay in the baseline and are not charged. Updates compare title, application links, department, locations, work labels, eligibility, source salary and full cleaned description. Routing links between career sites of the same shared tenant are normalized to job identity. Changing the output description length or the daily FX rate does not create an update. Closed jobs go to CLOSED_JOBS in the current run's default key-value store, with no job event charge.
Use a separate store for each company and filter scope. A scope mismatch fails without replacing existing state. Concurrent writers to the same store are rejected. Stores created by this Actor can be reused by name; select stateStoreId for a store created elsewhere. The Actor requests read/write access to your selected state store and read-only access to input datasets.
changedFields lists changed content fields, such as salary, description or remoteEligibility. firstSeenAt and lastSeenAt are observations in your store, not publication dates. Closed jobs are retained for 90 days to recognize reopening; later returns count as new. An initial failed detail followed by a successful read may be marked updated with an empty changedFields list.
Incomplete listings do not confirm closure. Failed details preserve the previous successful fingerprint. New or updated fingerprints are saved only after the job is written. Tracking reads full details even with descriptionFormat: none.
changeOutput: all also emits unchanged matching jobs; every written row is charged. changeTracking and previousDatasetId cannot be combined.
Catalog pages and export
Use operation: catalog to export up to maxCompanies rows to CATALOG, with no ATS requests, job dataset writes or job event charges. Rows include reference, name, jobCountAtCheck and lastVerifiedAt. Old entries have an unknown verification date (null); catalogGeneratedAt is not a board check date. Run the same input with catalogCursor set to nextCursor to read the next page.
searchAllCompanies: true uses the same stable alphabetical pages. Cursors belong to one catalog snapshot and platform selection; a refresh or platform change requires restarting pagination. Check scanComplete and retryCurrentPage before continuing a jobs scan. resumeCursor repeats an incomplete page, or advances after a complete one. A null resume cursor with retryCurrentPage: true means retry the first page. Accepted partial results stay in the output. Use smaller pages or larger work limits if a page repeatedly stops; a page cursor does not resume within one board. Tracking stores remain tied to the same selected board scope.
A bare company name must have one catalog match. Ambiguous inputs return ambiguous_company and candidate board references in COMPANIES.matches. Select the exact reference. companyList offers engineering-watchlist (Stripe, Palantir, OpenAI) and enterprise-sample (Bosch, NVIDIA). These are small example task lists, not complete industry coverage.
Runtime limits
| Limit | Default | Maximum |
|---|---|---|
maxRequests | 500 | 5,000 |
maxRunSeconds | 300 seconds | 3,600 seconds |
maxJobs | 1,000 | 10,000 |
| Dataset rows read, combined per run | — | 10,000 |
| HTTP response data, combined per run | — | 32 MiB |
Requests include retries, salary-rate lookup and company discovery. Time includes storage reads, job processing and throttle waits. Null work limits use the defaults. Memory is fixed at 512 MB. Large boards may need higher work limits and additional paid windows.
RUN_SUMMARY.stoppedBy reports the stopping reason: max_requests, max_run_seconds, max_response_bytes, max_jobs, charge_limit, scan_charge_limit, scan_charge_error or wall_time_limit. Already processed jobs can still be written after a read limit. The wall deadline is the effective time limit plus 60 seconds for output and cleanup. Rows already written remain available. Incomplete scans never confirm closure.
Storage operations do not count as HTTP requests. A shorter Apify platform timeout can stop a run before its final summary is saved.
Company identity and dates
companyId identifies an ATS tenant, or a company domain supported by native employer data or links from your supplied career page. companyNameSource, companyWebsiteSource and companyIdentitySource describe the evidence. Unknown names and websites stay null. Equal names alone do not link companies; domains, industry and company size are not guessed.
associatedBoardKeys lists related boards known when a row was written. COMPANY_IDENTITIES groups the board keys and names observed across the complete run, including unchanged tracked jobs.
publishedAt is a source publication date; createdAt is an ATS creation date. postedAt is the source date or relative estimate, with its origin in postedAtSource. updatedAt is a source modification date; scrapedAt is the current read. Tracking's firstSeenAt is separate from all source dates.
Output example
A fictional job:
{"jobKey": "greenhouse:acmerobotics:7001234","platform": "greenhouse","companySlug": "acmerobotics","companyName": "Acme Robotics","title": "Senior Backend Engineer, Payments","url": "https://job-boards.greenhouse.io/acmerobotics/jobs/7001234","department": "Engineering","locations": ["Austin, TX; Remote - US"],"countries": ["US"],"workplaceType": "remote","employmentType": "full_time","seniority": "senior","companyWebsite": "https://acmerobotics.example","salary": { "min": 160000, "max": 190000, "currency": "USD", "period": "year", "periodInferred": false, "source": "text", "text": "$160,000 - $190,000", "yearlyUsd": { "min": 160000, "max": 190000 } },"experienceYears": { "min": 5, "max": null, "text": "5+ years of experience" },"skills": ["Go", "PostgreSQL", "Kubernetes"],"visaSponsorship": "not_available","postedAt": "2026-10-01T14:05:11-04:00","description": "About Acme Robotics\n\nWe build ...","contactDataRemoved": 0}
The example shows selected fields. Each actual row contains the full output schema; unknown values use null or an empty collection. The table view Jobs and CSV export show the main columns. The default key-value store has COMPANIES and RUN_SUMMARY. A board can be partial: its list was usable, but a detail failed or its listing was incomplete. listingComplete describes board coverage; detailsComplete means no requested detail failed. detailRequests counts jobs sent for detail enrichment, not HTTP calls, because a bulk request can cover many jobs. detailFailures, per-item warnings, and RUN_SUMMARY.partialSuccess expose partial results.
Each job has detailStatus (complete, failed, not_requested) and descriptionStatus (complete, truncated, missing, omitted). A failed detail keeps available list fields. description_truncated means the requested text length limit cut the output. With descriptionFormat: none, the description is intentionally omitted.
Filters
| Filter | How it works |
|---|---|
| Title keywords / exclude | Whole words or phrases, any case ("go" does not match "Google") |
| Skills | Names from the 131-entry rule table; case insensitive, common aliases such as K8s accepted. skillsMatch: all (default) or any. For other terms, use description keywords |
| Description keywords | Whole words or phrases in the full plain text. descriptionKeywordsMatch: any (default) or all |
| Department keywords | Any whole word or phrase in the department; unknown departments are dropped |
| Visa sponsorship | Explicit available or not_available; unknown or conflicting statements are dropped |
| Location keywords | Text in the location ("Berlin", "Remote") |
| Countries | ISO codes. Jobs with an unknown country are dropped |
| Workplace, employment type, seniority | From the ATS fields first, then from the title and location |
| Posted within days | Jobs without an exact or usable date are dropped. A lower bound such as "30+ days" does not prove recency. This is a date window, not new-job detection |
| Remote countries / regions | Explicit remote eligibility, or explicit worldwide scope. Country and region lists each match any requested value; exclusions are respected. EU and EEA labels are expanded for country filtering, including Hungary; EEA adds Iceland, Liechtenstein and Norway. Known country/region exclusions are subtracted. Unknown regional exclusions make a country match unproven. Other regional labels remain labels |
| Worldwide remote only | Explicit worldwide wording with no detected country, region, timezone or geographic exclusion restriction |
| Max years of experience required | Uses required/unmarked experience only. Explicit preferred qualifications go to preferredExperienceYears. Unknown requirements are kept |
| Min / max salary per year (USD) | minYearlySalaryUsd and maxYearlySalaryUsd: salaryMatch: overlap (default) keeps advertised ranges that overlap the query. contained requires the whole advertised range inside it, with a known endpoint for each query bound. Unknown conversion is dropped. A single endpoint is used as a point for overlap |
Merge the same job in several cities (mergeLocations) combines postings only when the source gives a shared job identity and their complete descriptions and material fields match. This currently uses Greenhouse internal_job_id. Equal titles alone never merge jobs. Different descriptions, departments, pay or employment labels stay separate. Missing or failed details also stay separate. Merging reads complete details even when description output is omitted or shortened. An accepted merged row is charged once. otherJobIds keeps the other posting IDs; otherPostings keeps each additional posting's jobId, url, applyUrl, locations, countries and dates. The main URLs remain those of the first posting.
descriptionMaxLength limits plain-text output length. Filtering and change comparisons still use the full description.
Yearly USD salary uses annualization assumptions: 2,080 hours, 260 days, 52 weeks or 12 months per year. These assumptions can overstate part-time annual pay. USD amounts and the shipped fixed USD peg rates remain available when ECB is down. Other currencies need a valid ECB rate; their original salary stays available, but yearlyUsd is null with warning salary_usd_unavailable. A USD salary filter excludes such unavailable conversions.
Different filters combine with AND. List fields are checked first; filters that need details are checked after enrichment. Skills, description and visa filters read the full description even when output is omitted or truncated. Limits count final accepted output rows. Filtered-out jobs have no job event charge, but their detail requests still use compute.
remoteEligibility contains scope, countries, regions, excludedCountries, excludedRegions, timezones and the supporting evidence. Supported region labels are EU, EEA, Europe, EMEA, APAC and LATAM. officeLocations contains explicitly stated offices. Raw ATS locations and countries stay separate from eligibility. An office in the US does not make a job US-only; a remote label alone does not make it worldwide. These are text rules, so unknown or complex restrictions may remain unknown.
Pricing
Platform usage is included in these event prices. There is no separate platform-usage surcharge.
| Event | Free / Bronze | Silver | Gold / Platinum / Diamond |
|---|---|---|---|
| Job written, per 1,000 | $1.99 | $1.79 | $1.49 |
| Actor start, every run | $0.03 | $0.03 | $0.03 |
| Each additional started five-minute read window | $0.03 | $0.03 | $0.03 |
The start fee covers the first five minutes of read work and applies even to empty, failed and catalog-only runs. No job event is charged for filtered jobs, failed boards, duplicate jobs, closure records, catalog rows, or unchanged jobs in changes-only mode. A partial job row that passes the filters and is written is charged; inspect its quality fields.
Long reads are opt-in: increase maxRunSeconds above 300 and maxRequests when needed, within cloud bounds. Additional windows apply to ATS reads, company discovery, storage reads and job processing, including zero-change comparisons, retries and throttle waits. Concurrent work shares one window event. A window fee is not refunded if the run ends before using all of that window. Final output writes and storage cleanup do not buy another window. When another window is unaffordable or charging fails, the Actor stops reads, saves accepted partial results and cannot infer closures from an incomplete scan.
Examples on Free/Bronze: 1,000 jobs within five minutes cost $2.02; 20 minutes of read work with zero output cost $0.12 (one start and three additional windows). An unchanged daily check within five minutes costs $0.03 per day. At exactly the configured read deadline, the Actor does not buy an unused next window. The minimum run spending limit is $0.04. Set a spending limit that covers the start, expected output and any extra windows. Each written job has one job charge. See Apify PPE.
Source and data limits
- Public career boards only; no login, browser or AI enrichment. Some sources can deny access or omit fields. Inspect COMPANIES, job warnings and
listingCompletebefore treating a scan as complete. - Salary, skills, experience, visa and work labels use source fields and text rules. They can be unknown or misclassified; for example, a title containing "Remote Sensing" can trigger a remote label. Use explicit
remoteEligibilitywhen geographic permission matters. - Salary periods inferred from text are marked
periodInferred: true. Source pay and evidence remain available for review. - Company names may describe a subsidiary. Career-page metadata is source evidence, not independent verification of company ownership.
- Workday searches can return at most 2,000 jobs; larger boards are split by source facets when possible.
truncated: trueindicates incomplete coverage. SmartRecruiters can return an empty successful list for an unknown company. - SuccessFactors may provide neither locations nor dates. Some boards provide no company name. Paylocity and Breezy can use verified HTML description sections when structured data is absent;
descriptionSourceidentifies the source. - Rate limits and
Retry-Afterare respected. A host stops withretry_wait_budgetwhen its requested waits exceed 120 seconds; it is not retried early. - Country detection uses source fields, location text and GeoNames, licensed under CC BY 4.0. Ambiguous places can have no country.
- Recognized personal contact emails, phone numbers and names are removed from descriptions. Shared career mailboxes can remain. This rule-based cleanup cannot guarantee removal of all personal data.