# Greenhouse, Lever, Ashby & Workday Jobs Scraper - 9 ATS API (`make_no_mistakes/multi-ats-job-board-api`) Actor

Scrape jobs from Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Recruitee, Rippling and Personio in one run. Give a company name or domain and it auto-detects the ATS and board slug - no token needed. One schema with salary, department, location, remote flag and apply URL.

- **URL**: https://apify.com/make\_no\_mistakes/multi-ats-job-board-api.md
- **Developed by:** [Make No Mistakes LLC](https://apify.com/make_no_mistakes) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 job scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Greenhouse, Lever, Ashby & Workday Jobs Scraper - 9 ATS API

**Does it scrape your ATS?** Greenhouse, Ashby, Lever, Workday, SmartRecruiters, Recruitee,
Rippling, Workable and Personio — nine applicant tracking systems, one run, one schema.

**Give it company names or domains. It finds the ATS.**

Every other job-board Actor makes you supply the board token — you have to already know that Stripe
is on Greenhouse under `stripe`, OpenAI is on Ashby under `openai`, and NVIDIA is on Workday under
`nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`. That lookup is the actual work, and nobody
ships it.

This one does. Hand it `["stripe", "openai.com", "Match Group"]` and it resolves each input to a
platform and a board slug, pulls every open job posting from that company's career page feed, and
returns one dataset item per job on a single normalized schema across all nine platforms — title,
department, team, city, country, remote flag, employment type, salary range, posted and updated
dates, full description and the real apply URL — with the untouched platform record under `raw`.
Board URLs and slugs work too, and `discoverOnly` mode answers "which ATS is this company on?"
for a fraction of a cent.

No HTML scraping, no proxies, no logins, no CAPTCHAs, no API keys. Just the job-board APIs the ATS
vendors published for aggregators to consume.

***

### For AI agents and MCP clients

Resolves company names or domains to their applicant tracking system and returns every open job on
one normalized schema across nine ATS platforms.

Minimal input that returns something useful:

```json
{ "companies": ["stripe", "openai", "ramp"], "maxItems": 200 }
```

Cheap mode — which ATS does each company use, without paying for jobs:

```json
{ "companies": ["stripe.com", "notion.so", "doordash.com"], "discoverOnly": true }
```

You get one dataset item per job, with `company`, `company_slug`, `ats`, `job_id`, `title`,
`department`, `team`, `location_raw`, `city`, `state`, `country`, `is_remote`, `employment_type`,
`salary_min`, `salary_max`, `salary_currency`, `salary_period`, `posted_at`, `updated_at`,
`apply_url`, `description_html`, `description_text`, `source_url` and `raw` (the untouched platform
record). In `discoverOnly` mode you get one item per company instead: `company_input`,
`company_slug`, `ats`, `board_url`, `job_count`, `resolved`, `other_matches`, `candidates_tried`.

Cost: $0.005 to start a run, plus $0.004 per job returned — or $0.001 per company in `discoverOnly`
mode. The first call above costs $0.005 if it finds nothing and $0.805 if it hits the 200-job cap.
**Set `maxItems` to bound the spend before you call.** A single large employer can have 2,000 open
jobs.

Typical questions it answers: which ATS does this company use; every open engineering role at these
40 companies; what changed on these boards in the last 7 days (`postedWithinDays: 7`); which of
these companies publish salary ranges and what they are; remote-only roles across a competitor set.

***

### The nine platforms

All nine were verified live before shipping. "Boards" is what a single request returns; "salary" is
whether the platform publishes a structured pay range at all, not whether a given employer filled
it in.

| Platform | Endpoint | Salary | Department / team | Dates |
|---|---|---|---|---|
| `greenhouse` | `boards-api.greenhouse.io/v1/boards/{token}/jobs` | ✅ pay ranges when the employer opts into transparency | department only | posted **and** updated |
| `ashby` | `api.ashbyhq.com/posting-api/job-board/{org}` | ✅ full structured comp — min, max, currency, interval | department **and** team | posted |
| `lever` | `api.lever.co/v0/postings/{org}` | ✅ `salaryRange` when filled in | department **and** team | posted |
| `smartrecruiters` | `api.smartrecruiters.com/v1/companies/{co}/postings` | ❌ | department + function | posted **and** updated |
| `recruitee` | `{co}.recruitee.com/api/offers/` | ⚠️ structured but usually empty | department | posted **and** updated |
| `rippling` | `api.rippling.com/platform/api/ats/v1/board/{co}/jobs` | ⚠️ `payRangeDetails`, rarely populated | department **and** sub-team | posted |
| `personio` | `{co}.jobs.personio.de/xml` | ❌ | department + recruiting category | posted |
| `workday` | `{tenant}.wd{N}.myworkdayjobs.com/wday/cxs/{tenant}/{site}/jobs` | ❌ | ❌ neither | posted |
| `workable` | `apply.workable.com/api/v1/widget/accounts/{co}` | ❌ | department + function | posted |

#### Measured field coverage

Percentage of returned jobs with a non-null value, on a live sample of 40 jobs per board,
2026-09-02. A dash means the platform does not publish the field at all.

| Board | Dept | Team | City | Country | Remote flag | Type | Salary | Posted | Updated | Description |
|---|---|---|---|---|---|---|---|---|---|---|
| greenhouse / `gitlab` | 100% | — | 55% | 80% | ✅ | — | **22%** | 100% | 100% | 100% |
| greenhouse / `stripe` | 100% | — | 80% | 45% | ✅ | — | 0% | 100% | 100% | 100% |
| ashby / `ramp` | 100% | 100% | 95% | 100% | ✅ | 100% | **100%** | 100% | — | 100% |
| ashby / `openai` | 100% | 100% | 92% | 100% | ✅ | 100% | **90%** | 100% | — | 100% |
| lever / `matchgroup` | 100% | 100% | 98% | 100% | ✅ | 95% | **50%** | 100% | — | 100% |
| lever / `palantir` | — | 100% | 100% | 100% | ✅ | 100% | 0% | 100% | — | 100% |
| smartrecruiters / `Ubisoft2` | 100% | 95% | 100% | 100% | ✅ | 100% | — | 100% | — | 100% |
| recruitee / `vandebron` | 100% | 100% | 100% | 100% | ✅ | 100% | 23% | 100% | 100% | 100% |
| rippling / `rippling` | 100% | 100% | 95% | 98% | ✅ | 100% | 0% | 100% | — | 100% |
| personio / `wandelbots` | 100% | 100% | 67% | — | ✅ | 100% | — | 100% | — | 0% |
| workday / `nvidia` | — | — | 100% | 100% | ✅ | 100% | — | 100% | — | 100% |
| workday / `salesforce` | — | — | 100% | 100% | ✅ | 100% | — | 100% | — | 100% |
| workable / `adverity` | 100% | 64% | 86% | 100% | ✅ | 86% | — | 100% | — | 100% |

The honest summary: **if you want salary data, Ashby is the platform that has it** — Ramp publishes
a range on 100% of its postings and OpenAI on 90%. Greenhouse has ranges only where the employer
turned on pay transparency (GitLab 22%, Stripe 0%). Lever is a coin flip. The other six publish no
usable pay data at all, and this Actor returns `null` rather than guessing a number out of the
description text.

***

### Discovery

Discovery is the point of this Actor, so here is exactly what it does and how well it works.

Each input is reduced to a short list of plausible board slugs — the domain label, the company name
lowercased and de-spaced, the hyphenated variant, the variant with `Inc` / `GmbH` / `Group` and
friends removed. Those are probed across the platforms in three tiers, cheapest first, stopping at
the first tier that finds a board of meaningful size:

1. Greenhouse, Ashby, Lever, SmartRecruiters, Recruitee, Rippling — one cheap GET each, all
   candidates in parallel.
2. Personio and Workable — both rate-limit by IP, so they are paced and only reached when tier 1
   comes up empty (or turns up only a handful of jobs).
3. Workday — needs per-tenant host and site discovery, so it goes last.

A board URL short-circuits the whole thing: paste `https://jobs.lever.co/matchgroup` or
`https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite` and the platform and slug are read
straight off it.

Every resolution is cached in the run's key-value store under `ATS_DISCOVERY_CACHE`, keyed by the
raw input string, so an unchanged watchlist does not re-probe.

**Measured hit rate: 18 of 20 (90%)** on a mixed test set — Stripe, OpenAI, Anthropic, Databricks,
Ramp, Notion, Linear, Datadog, NVIDIA, Vercel, Figma, Airtable, Brex, Cloudflare, Match Group,
Vandebron, Adverity, scale.com. **Misses: DoorDash and Retool** — both run career sites that are
not on any of the nine public job-board APIs, so there is nothing to find.

#### Where discovery gets it wrong

- **A slug can belong to someone else.** `scale.com` resolves to a six-job Personio board owned by a
  different company called Scale; Scale AI's real board is on Greenhouse under `scaleai`. When the
  slug guess is ambiguous there is no signal that distinguishes them. If the resolved `job_count`
  looks absurd for the employer, pass the board URL instead.
- **Some companies are on two platforms at once.** Mid-migration boards are common. The larger board
  wins, the other is listed in `other_matches` in `discoverOnly` mode, and jobs that appear on both
  are de-duplicated by title and location.
- **A rate-limited probe reads as a miss.** Personio and Workable both answer HTTP 429 when several
  requests land close together. The Actor paces and retries them, but a company whose only board is
  on one of those two can occasionally come back unresolved. Re-run, or pass the board URL.
- **`resolved: false` is the honest answer**, not an error. `candidates_tried` tells you which slugs
  were probed so you can supply the right one.

***

### Input

```json
{
  "companies": ["stripe", "openai.com", "Match Group", "https://jobs.ashbyhq.com/ramp"],
  "maxItems": 500,
  "postedWithinDays": 7,
  "titleIncludes": ["engineer", "designer"],
  "location": "United States",
  "remoteOnly": false,
  "includeDescription": true
}
```

| Field | Type | Default | Notes |
|---|---|---|---|
| `companies` | array of string | `["stripe","openai","ramp"]` | Names, domains, board slugs or board URLs. Mix them freely. |
| `discoverOnly` | boolean | `false` | Return one row per company — which ATS, which board, how many jobs — and stop. Charged as `company-resolved`, not `job-scraped`. |
| `platforms` | array of enum | all nine | Restrict discovery and fetching to a subset. |
| `maxItems` | integer | `500` | Hard cap across **all** companies combined, split evenly between the ones that resolved so no single board eats the budget. Unused allowance rolls forward. `0` = unlimited. |
| `postedWithinDays` | integer | — | Incremental mode. Keeps a job when the newer of `posted_at` / `updated_at` falls inside the window. |
| `titleIncludes` | array of string | — | OR'd, case-insensitive, matched against the job title. |
| `location` | string | — | Substring match against `location_raw`, `city`, `state` and `country`, case-insensitive. |
| `remoteOnly` | boolean | `false` | Keep only jobs the platform flags remote, or whose location says remote / anywhere / distributed. |
| `includeDescription` | boolean | `true` | Off omits `description_html` and `description_text` entirely — items get roughly 10× smaller. |

`maxItems` is your spend bound. Set it deliberately: 20 well-known tech companies is about
5,000 open jobs, and a single Workday tenant can be 2,000 on its own.

***

### Output

One item per job. Every item carries all 24 fields; anything the platform doesn't publish is `null`
rather than missing, so the dataset exports cleanly to CSV or a database without ragged columns.

```json
{
  "company": "Stripe",
  "company_slug": "stripe",
  "ats": "greenhouse",
  "job_id": "8044460",
  "title": "AI Engineer",
  "department": "1150 Solutions Architecture",
  "team": null,
  "location_raw": "Chicago",
  "city": "Chicago",
  "state": null,
  "country": null,
  "is_remote": false,
  "employment_type": null,
  "salary_min": null,
  "salary_max": null,
  "salary_currency": null,
  "salary_period": null,
  "posted_at": "2026-07-03",
  "updated_at": "2026-08-26",
  "apply_url": "https://stripe.com/jobs/search?gh_jid=8044460",
  "description_html": "<h2><strong>Who We Are</strong></h2>…",
  "description_text": "Who We Are\n\nAbout Stripe\n\nStripe is a financial infrastructure platform…",
  "source_url": "https://boards-api.greenhouse.io/v1/boards/stripe/jobs",
  "raw": { "internal_job_id": 3486653, "requisition_id": "See Opening ID", "offices": [], "…": "…" }
}
```

A record from Ashby, where the compensation block is real:

```json
{
  "company": "Ramp", "ats": "ashby", "title": "Security Engineer, Cloud",
  "department": "Engineering", "team": "Backend",
  "location_raw": "New York, NY (HQ) | Remote (Canada) | Remote (US) | Miami, FL",
  "city": "New York City", "state": "NY", "country": "US", "is_remote": true,
  "employment_type": "FULL_TIME",
  "salary_min": 211400, "salary_max": 290600, "salary_currency": "USD", "salary_period": "YEAR",
  "posted_at": "2026-04-07",
  "apply_url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application"
}
```

`raw` holds the untouched platform record (minus the description blobs, which are already broken
out above), so nothing an ATS publishes is lost even where this Actor's normalized schema has no
home for it.

#### Normalization rules worth knowing

- **`employment_type`** is mapped onto `FULL_TIME`, `PART_TIME`, `CONTRACT`, `TEMPORARY`,
  `INTERNSHIP`, `VOLUNTEER`. A value nobody recognises passes through upper-cased rather than being
  dropped.
- **`salary_period`** is `YEAR`, `MONTH`, `WEEK`, `DAY` or `HOUR`.
- **Salary comes only from structured compensation fields.** Descriptions are never regex-mined for
  pay. A guessed number in a `salary_min` column is worse than an honest `null`.
- **`is_remote`** is true when the platform sets a remote flag, or when `location_raw` contains
  remote / anywhere / distributed / work-from-home. Job titles and descriptions are never consulted
  — they say "remote" for reasons that have nothing to do with the role.
- **`city` / `state` / `country`** come from the platform's structured address where it publishes
  one (Ashby, SmartRecruiters, Recruitee, Workable) and from parsing `location_raw` otherwise. ATS
  location strings are unconstrained free text, so treat the parse as best-effort and
  `location_raw` as the truth.
- **Multiple locations** are joined with `|` in `location_raw`; `city`/`state`/`country` describe
  the primary one.
- **Dates are dates**, `YYYY-MM-DD`, in every field on every platform.
- **`company` is the employer's own name where the platform publishes one** — Greenhouse,
  SmartRecruiters, Recruitee, Rippling, Personio and Workable all do. Ashby, Lever and Workday do
  not, so `company` there is derived from what you passed in: `"Match Group"` stays `"Match Group"`,
  but a bare slug like `openai` becomes `"Openai"`. Pass the name you want to see, or read
  `company_slug`, which is always exact.

***

### Cost

Pay-per-event. You pay for what the run actually returns.

| Event | Price | When |
|---|---|---|
| Actor start | $0.005 | once per run |
| `job-scraped` | $0.004 | per job written to the dataset |
| `company-resolved` | $0.001 | per company in `discoverOnly` mode |

`job-scraped` and `company-resolved` are never both charged in the same run. Jobs filtered out by
`postedWithinDays`, `location`, `titleIncludes` or `remoteOnly` are never written and never billed.

Worked examples: resolving a 200-company watchlist with `discoverOnly` costs **$0.205**. Pulling
500 jobs costs **$2.005**. A daily `postedWithinDays: 1` sweep over 50 companies typically returns
a few dozen jobs, so it runs at cents a day rather than re-paying for the whole board — which is the
point of incremental mode.

***

### Honest limits

**Workday is the fragile one.** It is the only platform of the nine with no documented public
job-board API and no directory of tenants. Every employer lives on its own host (`wd1` … `wd12`)
under a site name it chose (`External`, `NVIDIAExternalCareerSite`, `Salesforce_Careers`, …), and
both have to be discovered. This Actor reads the real site name off each host's `robots.txt`, which
is reliable today, and falls back to probing common site names. **None of that is contract.** If
Workday changes its behaviour, Workday companies start coming back unresolved — the other eight
platforms are unaffected, and the run does not fail. Workday also publishes no department, no team
and no salary anywhere in its public feed, so those columns are always `null` for Workday jobs.

**Salary coverage is thin outside Ashby.** SmartRecruiters, Workable, Personio and Workday publish
no compensation on their public endpoints at all. Recruitee and Rippling have the fields but
employers almost never fill them in. See the coverage table above before you build anything that
assumes a pay range.

**Personio and Workable rate-limit by IP.** Both answer HTTP 429 when requests arrive close
together. Requests to them are paced and retried, but a large watchlist that leans heavily on those
two can see individual companies come back unresolved. Re-run, or pass board URLs.

**Discovery misses employers that aren't on these nine.** DoorDash and Retool both run career sites
with no public ATS job-board API. Neither is a bug; there is nothing to fetch.

**Personio's XML feed usually omits the description.** The `<jobDescriptions>` element is empty on
most boards, so `description_html` and `description_text` are frequently `null` there.

**Rippling repeats a job once per work location.** The board endpoint returns the same `uuid`
several times; the Actor merges the locations and de-duplicates on `(ats, job_id)`, so 16 board rows
can legitimately become 7 jobs.

**`updated_at` exists on three platforms only** — Greenhouse, SmartRecruiters and Recruitee.
Everywhere else it is `null` and `postedWithinDays` filters on `posted_at` alone.

**Job counts move.** Boards change hour to hour. A `job_count` from `discoverOnly` is a snapshot,
not a promise about the next run.

***

### How it works

- Calls each vendor's public job-board API directly — nine adapters, one per platform, in
  `src/platforms/`.
- Every company and every platform is fetched inside its own try/except: one dead source doesn't
  take down the run, and per-platform counts and an `errors` list are written either way.
- Failed requests retry with exponential backoff and honour `Retry-After`. A 404 board slug is
  treated as "not on this platform", not as an error.
- Jobs are de-duplicated on `(ats, job_id)`. When a company is live on two platforms, postings that
  match on title and location are dropped from the smaller board.
- A `RUN_SUMMARY` record is written to the default key-value store with the resolved filters, item
  counts per platform and per company, unresolved companies, and any per-source errors.
- Runs in 256–1024 MB. A 20-company sweep finishes in well under a minute.

***

### Data source and attribution

All data comes from job-board APIs the ATS vendors publish for exactly this purpose — Greenhouse's
is documented at `developers.greenhouse.io`, Ashby's and Lever's likewise. This Actor reads public
endpoints only: no authentication, no proxies, no HTML scraping, no browser.

**The employer is the system of record.** Job postings belong to the companies that published them,
and their content is theirs. This Actor does not correct, infer or enrich — it normalizes. If a
posting looks wrong, it looks that way on the employer's own careers page too. `source_url` and
`apply_url` on every item point back to where it came from; check the employer's and the ATS
vendor's terms for your use case before redistributing.

No personal data is collected. These are job postings, not candidates.

# Actor input Schema

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

Company names ("Match Group"), domains ("stripe.com"), board slugs ("stripe") or full board URLs ("https://jobs.lever.co/matchgroup"). Each entry is resolved to an ATS and a board slug automatically - you do not need to know which platform an employer uses.

## `discoverOnly` (type: `boolean`):

Return one row per company saying which ATS it uses, the board URL and how many jobs are open - and stop there. This is the cheap way to build or audit a watchlist before pulling jobs.

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

Restrict discovery and fetching to these ATS platforms. Leave empty to probe all nine.

## `maxItems` (type: `integer`):

Hard cap on dataset items across all companies combined - this is your spend bound. The budget is split evenly across the companies that resolved, and unused allowance rolls forward. Set to 0 for unlimited (20 large companies is easily 5,000+ jobs).

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

Incremental mode: keep only jobs whose posted or updated date falls inside this window. Leave empty for the whole board. Jobs on platforms that publish no date at all are kept rather than silently dropped, and counted in the run summary.

## `titleIncludes` (type: `array`):

Keep a job when its title contains any of these words (case-insensitive). Example: engineer, designer. Leave empty for every title.

## `location` (type: `string`):

Keep a job when this text appears in its location, city, state or country (case-insensitive). Example: United States, or Berlin, or NY.

## `remoteOnly` (type: `boolean`):

Keep only jobs flagged remote by the platform, or whose location says remote / anywhere / distributed.

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

On by default. Turn off to omit description\_html and description\_text - items get roughly 10x smaller. On SmartRecruiters this also skips the per-job request its description needs, so those boards get much faster; Workday and Rippling still fetch their per-job record either way, because that is where their location, employment type and posted date live.

## Actor input object example

```json
{
  "companies": [
    "stripe",
    "openai",
    "ramp"
  ],
  "discoverOnly": false,
  "maxItems": 500,
  "remoteOnly": false,
  "includeDescription": true
}
```

# Actor output Schema

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

One normalized item per open job across every resolved company and platform. Fields: company, company\_slug, ats, job\_id, title, department, team, location\_raw, city, state, country, is\_remote, employment\_type, salary\_min, salary\_max, salary\_currency, salary\_period, posted\_at, updated\_at, apply\_url, description\_html, description\_text, source\_url, raw. In discoverOnly mode the dataset holds one item per company instead: company\_input, company\_slug, ats, board\_url, job\_count, resolved, other\_matches, candidates\_tried. Field definitions and examples are in the dataset schema.

## `jobsCsv` (type: `string`):

The same items as a CSV download.

## `runSummary` (type: `string`):

RUN\_SUMMARY record in the default key-value store: resolved filters, item counts per platform and per company, unresolved companies, and any per-source errors.

## `discoveryCache` (type: `string`):

ATS\_DISCOVERY\_CACHE record in the default key-value store: every company input resolved during the run, keyed by the raw input string.

# 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": [
        "stripe",
        "openai",
        "ramp"
    ],
    "maxItems": 500
};

// Run the Actor and wait for it to finish
const run = await client.actor("make_no_mistakes/multi-ats-job-board-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": [
        "stripe",
        "openai",
        "ramp",
    ],
    "maxItems": 500,
}

# Run the Actor and wait for it to finish
run = client.actor("make_no_mistakes/multi-ats-job-board-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": [
    "stripe",
    "openai",
    "ramp"
  ],
  "maxItems": 500
}' |
apify call make_no_mistakes/multi-ats-job-board-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,make_no_mistakes/multi-ats-job-board-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/S5RWr0RpYSTV2EbMp/builds/t4vYmqLQxZ0CzSmEW/openapi.json
