# Career Site Jobs API: Greenhouse, Lever, Workday, Ashby ATS (`stippled_journey/ats-jobs`) Actor

Every open job from 18,120 company career sites, or any Greenhouse, Lever, Workday, Ashby or 8 more ATS boards you list. Seniority, skills, visa sponsorship and salary on every row. New, changed and removed jobs on a schedule. Pay per job.

- **URL**: https://apify.com/stippled_journey/ats-jobs.md
- **Developed by:** [Navista Multani](https://apify.com/stippled_journey) (community)
- **Categories:** Jobs, Developer tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 job rows

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

## Career Site Jobs API: Greenhouse, Lever, Workday, Ashby ATS

A job postings API for company career sites: get every open job straight from the applicant tracking systems companies post on (Greenhouse, Lever, Workday, Ashby and 10 more) in one clean format. Search 33,310 company boards by job title and location, or list the companies you want, and track new, changed and removed jobs on a schedule.

### Job postings API: what you can use it for

- **Build a job board, newsletter or job alert.** One schedule fills your site with fresh
  jobs straight from employers, already in one format, with the employer's own apply link.
- **Monitor company careers pages.** List the companies you follow (competitors,
  customers, portfolio companies) and get only new, changed and removed jobs each day.
- **Find jobs with visa sponsorship, a salary range or a seniority level.** Every row
  carries `visa_sponsorship`, `salary`, `seniority`, `experience_years_min` and `skills`,
  so you filter on them instead of reading descriptions.
- **Study the job market.** Who is hiring for what, where, at what pay and with which
  skills, in columns ready for a spreadsheet or a database.
- **Give an AI agent live job data.** Every row stands alone, so it drops into an LLM's
  context or a RAG index without joins; call it through the Apify API or MCP server.

### Job data API: sample output

One row per job, the same fields on all 14 platforms. A real row (Sonos on Workday,
recorded 2026-10-24; description shortened):

```json
{
  "id": "workday:sonos/sonos:Senior-Salesforce-Engineer_R2847",
  "source": "workday",
  "board": "sonos/Sonos",
  "company": "Sonos, Inc.",
  "title": "Senior Salesforce Engineer",
  "department": null,
  "team": null,
  "category": null,
  "employment_type": "full_time",
  "workplace_type": null,
  "location": "Boston, MA",
  "locations": ["Boston, MA"],
  "country": "United States of America",
  "salary": { "min": 165000, "max": 206000, "currency": "USD", "period": "year", "source": "description_text" },
  "salary_text": null,
  "seniority": "senior",
  "experience_years_min": 8,
  "visa_sponsorship": false,
  "skills": ["AWS", "CI/CD", "Git", "Test automation", "Salesforce", "CRM"],
  "posted_at": "2026-10-09T00:00:00Z",
  "updated_at": null,
  "url": "https://sonos.wd1.myworkdayjobs.com/Sonos/job/Boston-MA/Senior-Salesforce-Engineer_R2847",
  "apply_url": "https://sonos.wd1.myworkdayjobs.com/Sonos/job/Boston-MA/Senior-Salesforce-Engineer_R2847",
  "description_text": "At Sonos we want to create the ultimate listening experience for our customers ...",
  "content_hash": "bffcfc146e91ec6f",
  "scraped_at": "2026-10-27T15:07:13Z"
}
```

What each row holds:

- **Job**: `title`, `company`, `department`, `team`, `category`, `employment_type`, `url`, `apply_url`, `posted_at`, `updated_at`
- **Place**: `location`, `locations` (all of them), `country`, `workplace_type` (`remote`, `hybrid`, `onsite`)
- **Pay**: `salary` as `{min, max, currency, period, source}` (ISO currency, period year/month/week/day/hour), plus `salary_text` where the board shows a label
- **Read from the posting by fixed rules, on every row**:
  - `seniority`: intern, entry, mid, senior, lead, manager, director or executive, from the title
  - `experience_years_min`: the years of experience the description asks for ("5+ years" gives 5)
  - `visa_sponsorship`: `true` if the posting says it sponsors visas, `false` if it says it does not, `null` if it says neither
  - `skills`: tools and skills named in the posting (Python, AWS, Salesforce, SQL, Figma and about 480 more)
- **Text**: `description_text` (plain text), optionally `description_html`
- **Tracking**: stable `id`, `source` (the platform), `board`, `content_hash`, `scraped_at`, and in change mode `change` (`new`, `changed`, `removed`) and `removed_at`

Notes:

- `salary.source`: `structured` (the platform's salary fields), `pay_range_html` (the employer's pay-range block on Greenhouse), `label_text` or `description_text` (a pay range stated in text). `currency` is null when the text gives only `$` and the job is not only in the US; `period` is null when the source does not make it clear.
- `company`: Lever, Ashby and Gem feeds carry no company name, so it is read from the board's public page title (for example "Spotify"), else taken from the directory, else made from the board name. Pinpoint feeds have none either; there `company` is empty and `board` names the company.
- Pinpoint feeds have no posting date, so `postedWithinDays` drops Pinpoint jobs.
- `workplace_type` comes from the platform's own field where it has one, otherwise only `remote` or `hybrid` read from the location; it is never guessed as on-site.
- The `OUTPUT` record summarizes the run: boards loaded, not found or failed, jobs found and returned. A board that does not exist does not fail the run; a run where no board loads does.
- The dataset has three views: *Jobs*, *Seniority, skills and visa*, and *Changes*.

### Career site jobs from 14 ATS platforms: how it compares

- **A full live snapshot of each board, not a time window.** Each run reads the board's whole current job list, so you get every open job, not only jobs first seen in the last day or week.
- **Expired jobs included at no extra cost.** In change mode, jobs taken down since your last run come back as `removed` rows. You do not need a separate expired-jobs tool.
- **Direct employer postings.** Every row comes from the company's own career site, not from a job board's repost, so there are no duplicates from aggregators and the apply link is the employer's.
- **Seniority, years, visa and skills without an AI model.** The four fields are read by fixed, published word rules, so the same posting always gives the same answer, and a field stays empty rather than guessed. Hand-checked on 30 recorded postings (2026-10-27): no wrong seniority, years or visa value, and 126 of 130 skills correct; see the FAQ for what the rules miss.
- **Complete rows on every run.** No start fee, no per-run charge, no paid tier for the extra fields: every row has every field the source provides.
- **Large Workday boards in full.** Workday shows at most 2,000 jobs per search; larger boards are split automatically, so a 14,000-job board comes back complete.

What it is not: it does not cover hundreds of thousands of sites, and it does not add company data from other sources (headcount, funding, social profiles).

### Scrape job postings: ready-made examples

Open one, press Start, and change the input to suit you:

- [Software engineer jobs in the US from company career sites](https://apify.com/stippled_journey/ats-jobs/examples/software-engineer-jobs-us)
- [Remote data engineer jobs](https://apify.com/stippled_journey/ats-jobs/examples/remote-data-engineer-jobs)
- [Product manager jobs in London](https://apify.com/stippled_journey/ats-jobs/examples/product-manager-jobs-london)
- [Senior machine learning and AI engineer jobs](https://apify.com/stippled_journey/ats-jobs/examples/senior-machine-learning-jobs)
- [Internships and entry-level jobs](https://apify.com/stippled_journey/ats-jobs/examples/internships-and-entry-level-jobs)
- [Software jobs with published salary ranges](https://apify.com/stippled_journey/ats-jobs/examples/jobs-with-salary-ranges)
- [Engineering jobs in Berlin](https://apify.com/stippled_journey/ats-jobs/examples/jobs-in-berlin)
- [Monitor company careers pages for new and removed jobs](https://apify.com/stippled_journey/ats-jobs/examples/monitor-company-careers-page)

### Jobs with visa sponsorship

```json
{
  "keywords": ["engineer"],
  "visaSponsorship": true,
  "maxJobs": 100
}
```

Only postings that say they sponsor visas are kept; most postings say nothing either way,
so expect a small share of all jobs. To narrow by country, add `locations` (for example
`United States`, `USA`, `United Kingdom`, `Canada`, `Germany`). A location matches when
the job's location or country text contains it, and platforms write countries
differently, so list the usual spellings and the main cities.

### Pricing

Pay per job row returned, from $2.00 per 1,000 jobs. No start fee and no charge for jobs your filters drop. A removed-job row in change mode counts as one row. The run stops cleanly at your spending limit.

- Test run, 20 jobs: $0.04.
- Directory search for "data engineer" in Berlin and remote, 200 jobs: $0.40.
- Monitoring 25 company boards daily: a first full run of about 2,500 jobs costs $5.00; after that a daily change-mode run with about 50 new, changed or removed jobs costs about $0.10, about $3 a month.

### Job listing API input: examples and settings

#### Search the directory by title and location

```json
{
  "keywords": ["data engineer", "analytics engineer"],
  "locations": ["Berlin", "Remote"],
  "seniority": ["senior", "lead"],
  "postedWithinDays": 14,
  "maxJobs": 200
}
```

With no `boards`, the run searches the directory live, starting with the boards most likely to match (those whose titles and locations contained your words when the directory was built, largest first). It stops at `maxJobs` matches (500 when `maxJobs` is 0) or when `searchTimeLimitSecs` (default 150) runs out. At the time limit, boards still being read are cut off and the jobs from boards already read are returned, so the run ends close to the limit; its status says how many boards it searched and how many were cut off. Measured on Apify 2026-10-25: "data engineer" with `maxJobs` 100 returned 100 jobs from 69 boards in 30 seconds.

#### Read the companies you choose, in full

```json
{
  "boards": [
    "https://job-boards.greenhouse.io/dropbox",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/posthog",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "greenhouse:stripe"
  ],
  "workplaceTypes": ["remote"],
  "requireSalary": true
}
```

#### All input fields

| Field | What it does |
|---|---|
| `keywords` | Title words to look for (any of them). Without boards, these are the directory search. |
| `locations` | Keep jobs whose location or country contains any of these, for example `London`, `Remote`, `United States`. |
| `maxJobs` | Stop after this many jobs, newest first. `0`: no limit with boards, 500 for a directory search. |
| `workplaceTypes` | `remote`, `hybrid`, `onsite`. Jobs whose board does not say are dropped while this is set. |
| `seniority` | Keep these levels: `intern`, `entry`, `mid`, `senior`, `lead`, `manager`, `director`, `executive`. Titles with no level are dropped while this is set. |
| `postedWithinDays` | Keep jobs first published within this many days. |
| `visaSponsorship` | Keep only jobs whose posting says it sponsors visas. |
| `requireSalary` | Keep only jobs that publish pay. |
| `keywordsInDescription` | Also match keywords in descriptions. Broader, noisier, and slower on Rippling and Workday. |
| `boards` | Career-site URLs or short forms (see *Supported ATS platforms*). Empty: search the directory. |
| `onlyChanges` / `stateName` | Change mode (see below), and an optional name for a separate change history. |
| `includeDescription` / `includeHtml` | Plain-text description (on by default) and original HTML (off). |
| `maxJobsPerBoard` | At most this many jobs per board, newest first. |
| `searchSources` / `searchMaxBoards` / `searchTimeLimitSecs` | Directory search only: which platforms, how many boards (largest first), and the time limit. |
| `minRows` | Fail the run when it returns fewer rows than this, so a scheduled run that comes back short triggers your alerts. |

### Track new, changed and removed jobs on a schedule

Turn on `onlyChanges` and run the same input on a schedule. The first run returns every job as `change: "new"` and remembers them; each later run returns only:

- `new`: jobs that appeared since the last run,
- `changed`: jobs whose title, team, location, workplace type, salary, description or apply link changed,
- `removed`: jobs that are gone (filled or taken down), as `{"id", "source", "board", "change": "removed", "removed_at"}`.

A board that fails to load is skipped for that run; its jobs are not reported as removed.

Example: monitor three companies every morning.

1. Save this input as a task:

   ```json
   {
     "boards": ["greenhouse:stripe", "lever:spotify", "ashby:posthog"],
     "onlyChanges": true,
     "minRows": 0
   }
   ```

2. In Apify Console, create a schedule (for example daily at 07:00, cron `0 7 * * *`) and add the task to it.

3. Read each run's dataset, or connect a webhook or integration to send the rows on.

The change history is kept per input (boards plus filters). Use `stateName` to keep two separate histories for the same input.

### Supported ATS platforms and board URLs

| Platform | Paste a URL like | Short form |
|---|---|---|
| Workday | `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`, `https://wd1.myworkdaysite.com/recruiting/wf/WellsFargoJobs` | `workday:nvidia.wd5/NVIDIAExternalCareerSite` |
| Greenhouse | `https://job-boards.greenhouse.io/stripe` (EU boards too) | `greenhouse:stripe` |
| Lever | `https://jobs.lever.co/spotify` (EU boards too) | `lever:spotify` |
| Ashby | `https://jobs.ashbyhq.com/posthog` | `ashby:posthog` |
| Recruitee | `https://bunq.recruitee.com` | `recruitee:bunq` |
| Personio | `https://tado.jobs.personio.de` | `personio:tado` |
| Teamtailor | `https://tibber.teamtailor.com` | `teamtailor:tibber` |
| Workable | `https://apply.workable.com/huggingface` | `workable:huggingface` |
| Rippling | `https://ats.rippling.com/aalyria-careers/jobs` | `rippling:aalyria-careers` |
| JazzHR | `https://snapsheet.applytojob.com` | `jazzhr:snapsheet` |
| Gem | `https://jobs.gem.com/gem` | `gem:gem` |
| Pinpoint | `https://safetywing.pinpointhq.com` | `pinpoint:safetywing` |
| Join | `https://join.com/companies/join` | `join:join` |
| Paylocity | `https://recruiting.paylocity.com/Recruiting/Jobs/All/015e2e4d-77b3-437f-92d5-dc5b62d176d5` | `paylocity:015e2e4d-77b3-437f-92d5-dc5b62d176d5` |

All but Workday are in the searchable directory; Workday boards are read when you list them. Any page of a board works, even a single job's page (Paylocity: use the company's job list, since its job pages do not name the company). Boards on a company's own domain (for example `careers.example.com` served by one of these platforms) are not recognized; use the platform URL.

### FAQ

**Is there a free job postings API, and how is this different?**
Several platforms publish a free public feed per company: Greenhouse's Job Board API,
Lever's Postings API and Ashby's job board API each return one company's open jobs, in
that platform's own format. This Actor reads those feeds (and the career-site data of 11
more platforms), so you get a search across many companies by title and location, one row
format for all 14 platforms, the seniority, years, visa and skills fields, and change
tracking, without knowing each company's board in advance.

**How do I scrape job postings from a company's career site?**
Open the careers page, click any job, and copy the address if it is on one of the
platforms above (`job-boards.greenhouse.io`, `jobs.lever.co`, `myworkdayjobs.com` and so
on). Paste it into `boards`; any page of the board works.

**How fresh is the directory?**
It lists the boards found in Common Crawl's public web index, JazzHR's and Join's public sitemaps and public job lists on GitHub, each checked to have open jobs when it was built (2026-10-28; about 531,000 open jobs then). The jobs themselves are always read live. Companies not in it can be read by listing their board.

**Does a directory search read every board?**
It reads boards until it has `maxJobs` matches or reaches its time limit, likeliest boards first. A rare title may not reach every board within the limit; raise `searchTimeLimitSecs`, narrow `searchSources`, or list boards. In change mode a search does not stop at `maxJobs` (it still stops at its time limit, and boards it did not reach or cut off never count as removed). Boards with 1,000 or more open jobs are searched only on Greenhouse; list the others (about 21 boards, mostly recruiting agencies).

**How do I find jobs with visa sponsorship in the USA, UK or Canada?**
Turn on `visaSponsorship` and add the country to `locations` (see *Jobs with visa
sponsorship* above). Only postings that state sponsorship are returned; a posting that
says nothing is not counted as sponsoring.

**How are seniority, years, visa and skills decided?**

- `seniority` comes from title words only: "Senior", "Sr." give senior; "Staff", "Principal", "Lead" give lead; "Engineering Manager", "Manager, Marketing" give manager, but "Product Manager" or "Account Manager" stay empty because they are not people-management titles; "Head of" and "Director" give director; "VP" and "Chief ... Officer" give executive. A title without such a word stays empty.
- `experience_years_min` is the first number of years the description ties to experience; "10 years ago" or "a 4-year degree" never count.
- `visa_sponsorship` is set only by a sentence that ties sponsorship to a visa or work authorization; "executive sponsor" or sponsoring events never count, and a posting that says both yes and no gives `null`.
- `skills` match whole words from a fixed list of 486 tools and skills; short names that are also ordinary words (Go, R, C, Spark, Excel) count only in their usual form or in a list of tools. A skill named like the employer is left out (a Datadog posting does not list Datadog). A skill mentioned in the employer's own description of itself still counts.
- The rules read English best; German postings get seniority (Werkstudent, Praktikant, Junior, Senior) and years ("3 Jahre Berufserfahrung").

**Why is a filtered run slower on some platforms?**
Workday, Rippling, JazzHR, Join and Paylocity need one request per job for the description, date or pay. Title keywords, `seniority`, `postedWithinDays` and `maxJobsPerBoard` are applied before those requests; `visaSponsorship` and `requireSalary` need the full posting, so they are applied after.

**Can I use it from Python or an AI agent?**
Yes, through the Apify API (with the official Python or JavaScript client), the Apify MCP
server, or any Apify integration such as Make, Zapier or n8n.

**Does it return personal data?**
No. Email addresses and phone numbers are removed from descriptions, and recruiter or hiring-manager names are never returned.

**Why is SmartRecruiters missing?**
Its API's robots.txt disallows automated access, so SmartRecruiters URLs are reported as invalid boards.

### Data sources and terms

Every job is read from the public job feed or career-site API that the platform publishes for the company's own careers page, the same postings anyone can see there. No login, no browser and no proxy are used, and nothing is read that the board does not show publicly. Platforms whose robots.txt disallows the paths needed (SmartRecruiters) are not supported. Job postings belong to the employers; use the data in line with your own obligations, for example when you republish it.

# Actor input Schema

## `keywords` (type: `array`):

Job title words to look for, for example data engineer or account executive (case-insensitive; a job matches any of them). With no company boards listed, these search the built-in directory of 33,310 company career sites.

## `locations` (type: `array`):

Keep jobs whose location or country contains any of these, for example London, Remote or United States. Leave empty for every location.

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

Stop after this many jobs, newest first. 0 means no limit when you list boards; a directory search then stops at 500. Your run also stops at your spending limit.

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

Keep only these workplace types. Jobs whose career site does not state one are dropped while this is set.

## `seniority` (type: `array`):

Keep jobs at these levels, read from the job title (for example Senior, Staff, Head of, VP). Titles that do not state a level, such as plain Software Engineer, are dropped while this is set.

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

Keep jobs first published within this many days. Leave empty for every open job.

## `visaSponsorship` (type: `boolean`):

Keep only jobs whose posting says it sponsors visas (for example "We can sponsor visas"). Most postings say nothing either way and are dropped.

## `requireSalary` (type: `boolean`):

Keep only jobs that publish a pay range or amount.

## `keywordsInDescription` (type: `boolean`):

Also match the keywords in job descriptions, not only titles. Broader but noisier, and slower on Rippling and Workday boards.

## `boards` (type: `array`):

Leave empty to search the built-in directory. Or list career-site URLs on Workday, Greenhouse, Lever, Ashby, Workable, Recruitee, Personio, Teamtailor, Rippling, JazzHR, Gem, Pinpoint, Join or Paylocity (for example https://job-boards.greenhouse.io/stripe or https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite), or short forms like greenhouse:stripe or lever:spotify. Every open job on each board is returned, then filtered.

## `onlyChanges` (type: `boolean`):

Return only jobs that are new or changed since the last run with the same input, plus a row for each job that was taken down (expired). Schedule the run to monitor boards. Each row gets a change field: new, changed or removed.

## `stateName` (type: `string`):

Optional name for the change history, to keep separate histories for the same input. By default it is derived from the boards and filters.

## `includeDescription` (type: `boolean`):

Plain-text job description. Email addresses and phone numbers are removed.

## `includeHtml` (type: `boolean`):

Also return the original description HTML (contact details removed).

## `maxJobsPerBoard` (type: `integer`):

At most this many jobs from each board, newest first. 0 means no limit.

## `searchSources` (type: `array`):

Directory search only: search boards on these platforms. Leave empty for all thirteen.

## `searchMaxBoards` (type: `integer`):

Directory search only: search at most this many boards, largest first. 0 searches the whole directory.

## `searchTimeLimitSecs` (type: `integer`):

Directory search only: the search ends after this many seconds and returns the jobs found so far; boards still being read at that point are cut off and counted in the run status. The boards most likely to match are searched first.

## `minRows` (type: `integer`):

Mark the run as failed when it returns fewer rows than this, so a scheduled run that comes back short triggers your Apify alerts. 0 turns the check off.

## Actor input object example

```json
{
  "keywords": [
    "data engineer"
  ],
  "maxJobs": 20,
  "visaSponsorship": false,
  "requireSalary": false,
  "keywordsInDescription": false,
  "onlyChanges": false,
  "includeDescription": true,
  "includeHtml": false,
  "maxJobsPerBoard": 0,
  "searchMaxBoards": 0,
  "searchTimeLimitSecs": 150,
  "minRows": 0
}
```

# Actor output Schema

## `results` (type: `string`):

The scraped jobs, one normalized row each.

## `enriched` (type: `string`):

The same rows with the enrichment columns: seniority, years of experience, visa sponsorship, skills, salary.

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

Counts for this run: rows pushed, what was read, and anything that failed or hit a cap.

# 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 = {
    "keywords": [
        "data engineer"
    ],
    "maxJobs": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("stippled_journey/ats-jobs").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 = {
    "keywords": ["data engineer"],
    "maxJobs": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("stippled_journey/ats-jobs").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 '{
  "keywords": [
    "data engineer"
  ],
  "maxJobs": 20
}' |
apify call stippled_journey/ats-jobs --silent --output-dataset

```

## MCP server setup

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

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/KiA8ZwQhjFgXG0370/builds/D43EROeOJSpYtuRKi/openapi.json
