# ATS Jobs API — Greenhouse, Lever, Ashby, Workday & more (`tildekai/ats-jobs-api`) Actor

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.

- **URL**: https://apify.com/tildekai/ats-jobs-api.md
- **Developed by:** [Attila Kis](https://apify.com/tildekai) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.49 / 1,000 jobs

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## ATS Jobs API — Greenhouse, Lever, Ashby, Workday & more

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:

```json
{ "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:

```json
{
  "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:

```json
{
  "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.

```json
{
  "companyDatasetId": "YOUR_COMPANY_DATASET_ID",
  "companyInputField": "website",
  "skills": ["Python", "Kubernetes"],
  "skillsMatch": "all",
  "visaSponsorship": "available"
}
```

CSV example for the default column:

```csv
company
acme.example.com
https://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.

```json
{
  "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:

```json
{
  "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](https://docs.apify.com/actors/publishing/monetize/pay-per-event).

### 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 `listingComplete` before 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 `remoteEligibility` when 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: true` indicates 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; `descriptionSource` identifies the source.
- Rate limits and `Retry-After` are respected. A host stops with `retry_wait_budget` when its requested waits exceed 120 seconds; it is not retried early.
- Country detection uses source fields, location text and [GeoNames](https://www.geonames.org/), licensed under [CC BY 4.0](https://creativecommons.org/licenses/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.

# Actor input Schema

## `companies` (type: `array`):

ATS board/job URLs, platform:name, catalog company names, or a company domain/career page with supported ATS links. EU Lever: lever:eu/name or jobs.eu.lever.co/name.

## `searchAllCompanies` (type: `boolean`):

Also read the companies in the built-in company list (largest boards first). Use title, country or other filters, or the output gets very large.

## `platforms` (type: `array`):

Read only these platforms. Empty: all platforms of this Actor.

## `maxCompanies` (type: `integer`):

How many companies of the built-in list to read when 'Search all known companies' is on.

## `titleKeywords` (type: `array`):

Keep jobs whose title has at least one of these words or phrases (any case, whole words: 'go' does not match 'Google').

## `excludeTitleKeywords` (type: `array`):

Drop jobs whose title has one of these words or phrases.

## `locationKeywords` (type: `array`):

Keep jobs whose location text contains one of these (for example Berlin, Remote).

## `countries` (type: `array`):

Two-letter country codes (US, GB, DE). Jobs with an unknown country are dropped when this is set.

## `workplaceTypes` (type: `array`):

Remote, hybrid or on-site, from the ATS field or the title and location text.

## `employmentTypes` (type: `array`):

From the ATS field or the title.

## `seniorities` (type: `array`):

From title words (Senior, Staff, Director, ...) and the ATS level.

## `postedWithinDays` (type: `integer`):

Keep jobs posted in the last N days. Jobs without a posting date are dropped when this is set. Use it for daily or weekly runs: you get only new jobs, without storage.

## `maxYearsRequired` (type: `integer`):

Drop jobs whose text asks for more years of experience than this ("5+ years of experience"). Jobs that state no requirement are kept. Needs the description, so it is read even with Description: none.

## `minYearlySalaryUsd` (type: `integer`):

Keep jobs whose annual USD salary reaches this value at its top. USD and shipped fixed USD peg rates work without ECB. Jobs without a salary or an available conversion are dropped. Hourly pay assumes 2,080 hours per year. Needs the description, so it is read even with Description: none.

## `previousDatasetId` (type: `string`):

Compare with the IDs in one earlier dataset. This is not a complete multi-run state. For scheduled new/updated/closed tracking, use optional changeTracking with your own named store. Dataset reads share a 10,000-row run limit with company imports and restart output. Oversized snapshots fail before item download. Keep the snapshot unchanged during a run.

## `descriptionFormat` (type: `string`):

Plain text, HTML, or none. Salary from the text and visa sponsorship need the description; with 'none' only the salary fields of the ATS are used, and Workday, SmartRecruiters, Workable and some Greenhouse runs are much faster.

## `descriptionMaxLength` (type: `integer`):

Cut plain-text descriptions to this many characters (for AI agents and small exports). Salary, visa and experience are read from the full text first. HTML is never cut.

## `maxJobs` (type: `integer`):

Stop after this many jobs in total.

## `maxJobsPerCompany` (type: `integer`):

At most this many jobs from one company.

## `mergeLocations` (type: `boolean`):

Merge only postings with a shared source job identity and matching full descriptions and material fields (currently Greenhouse internal_job_id). Equal titles alone do not merge. Reads full details even with Description: none. Locations and countries combine; otherJobIds and otherPostings retain extra IDs and application links. Charged once per accepted merged row.

## `skills` (type: `array`):

Skill names from the rule table and common aliases, case insensitive. Match all by default. Uses the full description.

## `skillsMatch` (type: `string`):

Require all selected skills, or at least one of them.

## `descriptionKeywords` (type: `array`):

Whole words or phrases in the full plain-text description, before output truncation.

## `descriptionKeywordsMatch` (type: `string`):

Require any selected description phrase, or all selected phrases.

## `departmentKeywords` (type: `array`):

Any whole word or phrase in the department. Missing departments are excluded.

## `visaSponsorship` (type: `string`):

Keep only an explicit matching sponsorship label. Unknown values are excluded.

## `remoteCountries` (type: `array`):

Match explicit remote eligibility countries or an explicit worldwide scope. Office countries do not count. Region labels are not expanded to countries.

## `remoteRegions` (type: `array`):

Match an explicit region such as EU, EEA, Europe, EMEA, APAC or LATAM.

## `remoteWorldwideOnly` (type: `boolean`):

Requires explicit worldwide wording without a detected country, region or timezone restriction.

## `changeTracking` (type: `boolean`):

Off by default. Save full change state in YOUR Apify account. Needs stateStoreName for a store created by this Actor, or stateStoreId for an existing store. Reads details even when description output is none.

## `stateStoreName` (type: `string`):

Use this OR stateStoreId with changeTracking. This Actor creates the named store in your account. For a store created outside this Actor, select stateStoreId to grant access. Use one store per company/filter scope.

## `changeOutput` (type: `string`):

Only used with changeTracking. New and updated: emit and charge changed matching jobs only. All: emit all matching jobs with changeStatus, and charge each output row.

## `companyCsvUrl` (type: `string`):

Public HTTP(S) CSV URL. Merged with companies and dataset input. Needs a header matching companyInputField. Up to 4 MB; no login.

## `companyDatasetId` (type: `string`):

Your dataset with company names, domains, career URLs or ATS links in companyInputField. Dataset reads share a 10,000-row run limit with previous snapshots and restart output, including blank rows. Oversized datasets fail before item download. Keep inputs unchanged during a run.

## `companyInputField` (type: `string`):

Column or top-level dataset field used for company CSV/dataset input.

## `operation` (type: `string`):

Export a catalog page without job events, or read live jobs.

## `companyList` (type: `string`):

Explicit example boards from the shipped catalog. Their current availability is checked at run time.

## `catalogCursor` (type: `string`):

Use nextCursor from CATALOG. The cursor requires the same catalog snapshot and platform selection.

## `maxRequests` (type: `integer`):

On Apify: default 500 attempts, hard maximum 5,000 even if a higher value is supplied. Includes retries, FX and discovery. Storage/API operations are excluded. Partial jobs are kept.

## `maxRunSeconds` (type: `integer`):

On Apify: default 300 seconds, hard maximum 3,600. The first 300 seconds are included in the start fee; each additional started 300-second read window costs $0.03. Storage reads, job processing, retries and host waits count. Storage cleanup has 60 extra seconds.

## `maxYearlySalaryUsd` (type: `integer`):

Upper end of the requested annual USD range. Unknown conversions do not match. salaryMatch controls overlap versus full containment.

## `salaryMatch` (type: `string`):

overlap: some advertised pay falls inside the requested range. contained: the full advertised range fits; a missing endpoint cannot prove its bound.

## `stateStoreId` (type: `string`):

Optional existing buyer-owned store. Select this OR stateStoreName with changeTracking. The Actor uses JOB_STATE and a store-specific lease queue.

## Actor input object example

```json
{
  "companies": [
    "https://job-boards.greenhouse.io/stripe"
  ],
  "searchAllCompanies": false,
  "platforms": [],
  "maxCompanies": 1000,
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "locationKeywords": [],
  "countries": [],
  "workplaceTypes": [],
  "employmentTypes": [],
  "seniorities": [],
  "descriptionFormat": "text",
  "maxJobs": 1000,
  "mergeLocations": false,
  "skills": [],
  "skillsMatch": "all",
  "descriptionKeywords": [],
  "descriptionKeywordsMatch": "any",
  "departmentKeywords": [],
  "remoteCountries": [],
  "remoteRegions": [],
  "remoteWorldwideOnly": false,
  "changeTracking": false,
  "changeOutput": "new_and_updated",
  "companyInputField": "company",
  "operation": "jobs",
  "salaryMatch": "overlap"
}
```

# Actor output Schema

## `jobs` (type: `string`):

One item per open job: title, company, location, countries, workplace type, seniority, salary, visa sponsorship, description.

## `overviewCsv` (type: `string`):

One row per job with the main columns.

## `companies` (type: `string`):

Company status, partial failures, listing/detail completeness and job counts.

## `summary` (type: `string`):

Company/job counts, new/updated/closed totals, partial success, detail failures and HTTP errors.

## `closedJobs` (type: `string`):

Closed jobs compared with previousDatasetId or optional buyer-owned change state; only complete board reads confirm closure.

## `catalog` (type: `string`):

Stable catalog page, snapshot, nextCursor and verification metadata. No job events for catalog-only export.

## `companyIdentities` (type: `string`):

Observed ATS employer-domain claims and buyer career-link associations. Source labels are preserved; names alone do not merge identities.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "companies": [
        "https://job-boards.greenhouse.io/stripe"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tildekai/ats-jobs-api").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "companies": ["https://job-boards.greenhouse.io/stripe"] }

# Run the Actor and wait for it to finish
run = client.actor("tildekai/ats-jobs-api").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "companies": [
    "https://job-boards.greenhouse.io/stripe"
  ]
}' |
apify call tildekai/ats-jobs-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tildekai/ats-jobs-api"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/7hP2yt17qZYPkg1AG/builds/Bo27Q1oDshrUZbCL0/openapi.json
