# ATS Jobs: Greenhouse, SmartRecruiters, Personio | $1.49/1k (`glasswing/ats-jobs-scraper`) Actor

Open jobs from company career boards on Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Personio and Teamtailor. Give board URLs or company names; get one normalized row per job: title, location, remote flag, salary, dates, apply link, advert text. No API keys, no login.

- **URL**: https://apify.com/glasswing/ats-jobs-scraper.md
- **Developed by:** [Raffy](https://apify.com/glasswing) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.49 / 1,000 job postings

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?

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

### What does ATS Jobs Scraper do?

ATS Jobs Scraper reads the **public job boards that companies host on eight applicant-tracking systems (ATS)** - Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Personio and Teamtailor - and returns **one normalized row per open job**. You give it a list of companies; it gives you every posting they have open right now, in the same columns whichever system the company uses: title, department, location, remote flag, employment type, salary range where the ATS publishes one, dates, posting and apply links, and the full advert as plain text.

It works as a **Greenhouse API, Lever API and Ashby API alternative in one call**: no API key, no account on any of the systems, no browser and no proxy. Each company is one plain HTTP request to the endpoint the company's own careers page uses, so a run over dozens of companies finishes in seconds and the data is first-hand - never a repost from an aggregator.

It does **not** log in, does not read private applicant data and does not return recruiter or hiring-manager names, e-mail addresses or phone numbers.

### Use cases for this job board API

- **Hiring-signal lead generation** - feed in your target-account list and see which accounts are hiring, for which teams and where. Ten open "Data Engineer" roles is a buying signal for data tooling; a first "Head of Security" role is one for security vendors. Filter with `titleKeywords` and you pay only for the roles that matter to you.
- **Job board and newsletter aggregation** - pull the openings of the companies you cover straight from the source, with stable `jobId`s for de-duplication and direct `applyUrl`s.
- **Recruiting and talent-market research** - track which departments grow at competitors, how much of their hiring is remote, and what pay ranges they publish.
- **Sales and investor research** - headcount plans show up on a careers page long before they show up anywhere else.
- **Automation** - schedule a daily run and send new rows to Google Sheets, Airtable, Supabase or a CRM through Apify integrations, webhooks, n8n, Make or Zapier. Diff on `ats` + `companySlug` + `jobId` to get "new jobs today".

### Supported applicant-tracking systems

| ATS | Example entry | Also accepted | Notes |
|---|---|---|---|
| Greenhouse | `https://boards.greenhouse.io/stripe` | `https://job-boards.greenhouse.io/stripe`, `greenhouse:stripe` | Pay range when the company publishes pay transparency ranges. |
| Lever | `https://jobs.lever.co/palantir` | `lever:palantir`, `https://jobs.eu.lever.co/<slug>`, `lever-eu:<slug>` | Workplace type, team and country on every posting. |
| Ashby | `https://jobs.ashbyhq.com/ramp` | `ashby:ramp` | Structured salary (min, max, currency, period) when the company shows compensation. |
| SmartRecruiters | `https://jobs.smartrecruiters.com/BoschGroup` | `smartrecruiters:BoschGroup` | Needs the company id from the board URL. The advert text takes one extra request per job. |
| Workable | `https://apply.workable.com/blueground` | `workable:blueground` | Company boards on apply.workable.com. |
| Recruitee | `https://bunq.recruitee.com` | `recruitee:bunq` | Use the `<company>.recruitee.com` address, not the company's own careers domain. |
| Personio | `https://holidu.jobs.personio.de` | `personio:holidu`, `.jobs.personio.com` | Read in English; monthly or yearly salary when published. |
| Teamtailor | `https://career.teamtailor.com` | `teamtailor:career.teamtailor.com` | Host-based: give the address of the careers site. |

You can also type **just the company name** (`stripe`, `ramp`). The Actor then tries the eight systems in the order of the table above until one lists jobs for that name, and records which one matched in the `ats` column. That costs up to 8 small requests per spelling of the name, so a board URL or `ats:slug` is faster and exact. When no system knows the name you get one free `not_found` row for that company - an entry never disappears silently.

### What data can ATS Jobs Scraper extract?

Every `ok` row has the same columns. A value the ATS does not publish is `null`; nothing is guessed from the advert text.

| Field | Type | Description |
|---|---|---|
| `status` | string | `ok`, `not_found` or `error` (see below) |
| `company` | string | Company display name as the ATS publishes it; derived from the slug for Lever and Ashby, which publish none |
| `companySlug` | string | The company's identifier on the ATS (the careers host name for Teamtailor) |
| `ats` | string | `greenhouse`, `lever`, `ashby`, `smartrecruiters`, `workable`, `recruitee`, `personio` or `teamtailor` |
| `jobId` | string | The posting's id on that ATS; unique together with `ats` and `companySlug` |
| `title` | string | Job title |
| `department` | string | Department, when published |
| `team` | string | Team within the department (Lever, Ashby) |
| `location` | string | Primary location as displayed on the board |
| `locations` | array | Every location listed for the posting, primary first |
| `country` | string | ISO code (Lever, SmartRecruiters, Recruitee, Workable, Teamtailor) or country name (Ashby); `null` for Greenhouse and Personio |
| `workplaceType` | string | `remote`, `hybrid` or `onsite`, only when the ATS has an explicit workplace field (Lever, Ashby, SmartRecruiters, Recruitee) |
| `isRemote` | boolean | The ATS's remote flag; for Greenhouse, Personio and Teamtailor, whether the location text says Remote |
| `employmentType` | string | `Full-time`, `Part-time`, `Contract`, `Temporary`, `Internship`, `Permanent`, or the ATS's own wording |
| `salaryMin`, `salaryMax` | number | Pay range from the ATS's structured pay fields only |
| `salaryCurrency` | string | ISO 4217 code, e.g. `USD` |
| `salaryPeriod` | string | `year`, `month`, `week`, `day` or `hour`, when stated |
| `postedAt` | string | When the posting was published, ISO 8601 UTC |
| `updatedAt` | string | When it was last updated (Greenhouse, Recruitee) |
| `url` | string | Public posting URL |
| `applyUrl` | string | Direct link to the application form, when published |
| `descriptionText` | string | Full advert as plain text; `null` when `includeDescription` is off |
| `sourceEndpoint` | string | The public ATS endpoint the row was read from |
| `scrapedAt` | string | ISO 8601 time of extraction |
| `error` | string | Reason, on `not_found` and `error` rows only |

SmartRecruiters rows can also carry `descriptionError`: the reason why the advert text could not be fetched. The rest of that row is still complete.

#### Result status (tri-state output)

| `status` | Meaning | Billed? |
|---|---|---|
| `ok` | One open job posting. | Yes |
| `not_found` | The company has no board on the checked system(s), its board lists no open jobs, or no job matched your filters. One row per company, with the reason in `error`. | No |
| `error` | The board could not be read after retries (network error, block, or a changed response format). `error` says why. | No |

### How to scrape Greenhouse, Lever, Ashby and other ATS job boards

1. Open the Actor in Apify Console and click **Try for free**.
2. Put one company per line into **Companies or job board URLs**: a board URL, an `ats:slug` pair, or a company name.
3. Optionally narrow the run with **Title keywords**, **Location contains**, **Remote jobs only** or **Posted within (days)**. Filters run before a row is written, so you only pay for matches.
4. Set **Maximum results** (the default 20 keeps a first run cheap) and, if one large employer should not use up the run, **Maximum results per company**.
5. Click **Start**. Records appear in the dataset in real time; open the **Output** tab or **Export** them as JSON, CSV, Excel, XML or HTML.

To automate it, use the **API** tab (Node.js, Python, curl examples) or add a **Schedule**.

### Input

See the **Input** tab for the full schema. The main options:

| Field | Type | Default | Description |
|---|---|---|---|
| `companies` | array of strings | three sample boards | Board URLs, `ats:slug` pairs or company names, one per entry |
| `titleKeywords` | array of strings | - | Keep a job when its title contains any of these (case-insensitive) |
| `locationContains` | string | - | Keep a job when its location, extra locations or country contains this text |
| `remoteOnly` | boolean | `false` | Keep only remote postings |
| `postedWithinDays` | integer | - | Keep only jobs published in the last N days (falls back to the updated date) |
| `maxItems` | integer | `20` | Stop after this many rows |
| `maxItemsPerCompany` | integer | - (prefilled `7`) | Cap per company |
| `includeDescription` | boolean | `true` | Add `descriptionText`; turn off for lighter, faster runs |
| `startUrls` | array of strings | the same three boards | Alias of `companies` for tools that always send `startUrls` |
| `proxyConfiguration` | object | Apify Proxy off | Not needed; the boards answer datacenter IPs |

Example input (the prefilled default):

```json
{
    "companies": ["greenhouse:duolingo", "ashby:linear", "lever:spotify"],
    "maxItems": 20,
    "maxItemsPerCompany": 7,
    "includeDescription": true
}
```

A hiring-signal run over your own account list:

```json
{
    "companies": ["https://boards.greenhouse.io/stripe", "https://jobs.ashbyhq.com/ramp", "palantir", "smartrecruiters:BoschGroup"],
    "titleKeywords": ["data engineer", "analytics", "machine learning"],
    "postedWithinDays": 30,
    "includeDescription": false,
    "maxItems": 500
}
```

### Output

You can download the dataset in various formats such as JSON, HTML, CSV or Excel. Real rows from a run with the default input on 2026-09-20 (`descriptionText` shortened here), followed by the real `not_found` row a run gave for a company name that no system knows:

```json
[
    {
        "status": "ok",
        "company": "Duolingo",
        "companySlug": "duolingo",
        "ats": "greenhouse",
        "jobId": "8705196002",
        "title": "Ad Sales Lead - West",
        "department": "Business",
        "team": null,
        "location": "Remote - California",
        "locations": ["Remote - California"],
        "country": null,
        "workplaceType": null,
        "isRemote": true,
        "employmentType": null,
        "salaryMin": 152000,
        "salaryMax": 228000,
        "salaryCurrency": "USD",
        "salaryPeriod": null,
        "postedAt": "2026-08-13T13:22:34.000Z",
        "updatedAt": "2026-09-03T16:23:10.000Z",
        "url": "https://careers.duolingo.com/jobs/8705196002?gh_jid=8705196002",
        "applyUrl": "https://careers.duolingo.com/jobs/8705196002?gh_jid=8705196002",
        "descriptionText": "Our mission at Duolingo is to develop the best education in the world and make it universally available. ...",
        "sourceEndpoint": "https://boards-api.greenhouse.io/v1/boards/duolingo/jobs?content=true&pay_transparency=true",
        "scrapedAt": "2026-09-20T19:16:13.558Z"
    },
    {
        "status": "ok",
        "company": "Spotify",
        "companySlug": "spotify",
        "ats": "lever",
        "jobId": "2193db3f-77c5-43b8-b030-8f92c9882bf1",
        "title": "Android Engineer - Experience",
        "department": "Engineering",
        "team": "Experience",
        "location": "London",
        "locations": ["London", "Stockholm"],
        "country": "GB",
        "workplaceType": "hybrid",
        "isRemote": false,
        "employmentType": "Permanent",
        "salaryMin": null,
        "salaryMax": null,
        "salaryCurrency": null,
        "salaryPeriod": null,
        "postedAt": "2026-06-23T11:29:45.805Z",
        "updatedAt": null,
        "url": "https://jobs.lever.co/spotify/2193db3f-77c5-43b8-b030-8f92c9882bf1",
        "applyUrl": "https://jobs.lever.co/spotify/2193db3f-77c5-43b8-b030-8f92c9882bf1/apply",
        "descriptionText": "We design Spotify’s consumer experience—end to end, moment to moment, across every screen, platform, and partner integration. ...",
        "sourceEndpoint": "https://api.lever.co/v0/postings/spotify?mode=json",
        "scrapedAt": "2026-09-20T19:16:16.121Z"
    },
    {
        "url": "https://apify.com/glasswing/ats-jobs-scraper",
        "status": "not_found",
        "error": "No public job board was found for \"zzqq no such company 123\" on any of the supported systems (greenhouse, lever, ashby, smartrecruiters, workable, recruitee, personio, teamtailor). Give the board URL or \"ats:slug\" (for example greenhouse:stripe) if you know where the company posts its jobs.",
        "scrapedAt": "2026-09-20T19:19:11.702Z",
        "company": "zzqq no such company 123",
        "companySlug": "zzqqnosuchcompany123",
        "ats": null,
        "sourceEndpoint": "https://api.smartrecruiters.com/v1/companies/zzqqnosuchcompany123/postings?limit=1"
    }
]
```

### How much does it cost to scrape ATS job boards?

This Actor uses **pay-per-event** pricing:

| Event | Price | Charged when |
|---|---|---|
| Actor start | $0.005 per run | Once when the run starts. The platform charges one event per GB of memory and the Actor runs on 1 GB, so this is one event. |
| Job posting | $0.00149 per job | One job posting with `status: ok` was saved to the dataset. |

**1,000 jobs cost about $1.50** ($0.005 to start plus 1,000 x $0.00149 = $1.495); 100 jobs cost about $0.15 ($0.154), and the default 20-job run about $0.03 ($0.0348). You are never charged for a row with `status` `not_found` or `error`, never for a job your filters excluded, and never twice for the same posting in one run. The free Apify plan's $5 of monthly credit covers about 3,350 jobs in a single run.

You can cap spending per run with **Maximum results** and with the run's **Max total charge** option; the Actor stops gracefully when either limit is reached and keeps everything it has already saved.

### Integrations, exports and API access

Results export to **CSV, JSON, Excel**, XML and HTML from the **Output** tab or the dataset API. From there, Apify integrations and webhooks push the same rows into **Google Sheets, Airtable or a CRM**, plus Slack, Amazon S3 or your own endpoint - directly, or through Zapier, Make and n8n.

The Actor can also be called from the **Apify API** (Node.js, Python and curl snippets are on the **API** tab), from the **Apify MCP server**, or by any AI agent that can call a tool: inputs are plain strings, numbers and booleans, a call with no arguments runs the three sample boards, and every row carries `status` and `error`, so an agent can tell "this company is not hiring" apart from "this board could not be read" without parsing prose.

### Tips

- Prefer a board URL or `ats:slug` over a bare name: it is exact and saves the auto-detection requests.
- Put all your companies in one run instead of many single-company runs; you pay the start fee once and the boards are read in parallel.
- Set `includeDescription` to `false` when you only need the hiring signal (company, title, department, location). Greenhouse and Workable responses get much smaller, SmartRecruiters needs no per-job request, and the price per row is unchanged.
- Use `maxItemsPerCompany` when one employer in the list has thousands of openings.
- Schedule a daily run with `postedWithinDays: 1` for a "new jobs today" feed.

### Limitations

- **It needs the company list.** The Actor reads the boards you name; it is not a keyword search across all companies on an ATS. Finding out which ATS a company uses is on you, or on the auto-detection, which only tries the company name as a slug.
- **Auto-detection is a best effort.** It matches a name only when the company's slug on the ATS is the name itself (`stripe`) or its compact or hyphenated form (`bosch group` -> `boschgroup`, `bosch-group`), and a different company can own the same slug. At most 100 bare names are auto-detected per run. When it matters, give the board URL.
- **Salary only where the ATS publishes structured pay.** Ashby compensation, Greenhouse pay ranges, and the salary fields of Lever, Recruitee, Personio and Teamtailor are read; pay that an employer only mentions inside the advert text stays in `descriptionText`. Workable and SmartRecruiters publish no structured pay.
- **Fields differ by system.** `department`, `team`, `country`, `workplaceType`, `employmentType` and `updatedAt` are `null` where the ATS has no such field; the field table above says which. Lever and Ashby publish no company display name, so `company` is derived from the slug there.
- **SmartRecruiters**: about 5,000 postings per company per run at most, and the advert text costs one extra request per job.
- **Personio** is read in English (`language=en`); a tenant that writes its adverts only in another language can come back with an empty `descriptionText`.
- **Recruitee** boards must be given as `<company>.recruitee.com`; a company's own careers domain does not serve the public endpoint.
- **A board URL with a typo** gives a `not_found` row that carries the endpoint URL and `HTTP 404` rather than the company columns.
- Only published, public postings are visible. If a system changes its response format, rows come back as `error` rather than silently wrong; report it in the **Issues** tab and the Actor is updated quickly.

### FAQ

**Do I need an API key for Greenhouse, Lever, Ashby or the other systems?** No. The Actor reads the public, keyless endpoints that the companies' own careers pages use. All you need is an Apify account.

**How do I know which ATS a company uses?** Open its careers page and look at the address of a job or of the "Apply" button: `boards.greenhouse.io`, `jobs.lever.co`, `jobs.ashbyhq.com`, `jobs.smartrecruiters.com`, `apply.workable.com`, `*.recruitee.com`, `*.jobs.personio.de` and `*.teamtailor.com` are the giveaways. Paste that URL as it is. Or type the company name and let the Actor try.

**Can I search all companies for a keyword?** No. This Actor goes deep on the companies you list rather than wide across a whole system. `titleKeywords` filters the jobs of those companies.

**Does it return recruiter names, e-mails or phone numbers?** No. Some of these systems include the posting's creator, owner or a mailbox address in their responses; the Actor never copies those fields, and it masks e-mail addresses and phone numbers that an employer typed into the advert text.

**Why is `salaryMin` empty for most rows?** Most employers do not publish pay as a structured field. The Actor does not guess numbers out of prose, so an empty value means "not published in a structured way", never "unknown to us".

**Why did I get fewer rows than `maxItems`?** The companies had fewer open jobs, the filters excluded the rest, `maxItemsPerCompany` capped them, or the run hit your **Max total charge** limit. Check the run log for "Stopping gracefully".

**What happens when a company has no open jobs or nothing matches my filters?** You get one free `not_found` row for that company with the reason, so your pipeline can tell "not hiring" from "not checked".

**Can I use this Actor from an AI agent or MCP client?** Yes. Inputs are plain values, every row is self-describing (`status` plus `error`), and results can be fetched via the dataset API or the Apify MCP server.

### Legal and data-protection notice

This Actor extracts only data that companies publish on their public job boards, and only business data about **jobs and hiring companies**: job titles, adverts, departments, locations, workplace and employment types, pay ranges, dates, company names and links.

It does **not** emit recruiter, hiring-manager, creator or owner names, e-mail addresses or phone numbers. Where a system's public response contains such fields the Actor does not copy them, and as an extra safeguard e-mail addresses and phone numbers that an employer typed into the advert text are replaced with `[email removed]` and `[phone removed]` before the row is saved. The Actor does not log in, does not solve captchas and does not circumvent access controls.

Job adverts can nevertheless mention individuals. Personal data is protected by the GDPR in the European Union and by other regulations around the world; you should not process personal data without a legitimate reason. If you are unsure whether your reason is legitimate, consult your lawyers. You are responsible for complying with the terms of service of the systems and companies whose boards you read, and with applicable law, when you use the extracted data.

This Actor is an independent tool. It is not affiliated with, endorsed by or sponsored by Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Personio, Teamtailor or any company whose jobs it lists. All trademarks belong to their respective owners.

# Changelog

This Actor's version history is a separate document: https://apify.com/glasswing/ats-jobs-scraper/changelog.md

# Actor input Schema

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

Each entry is one of: a board URL (https://boards.greenhouse.io/stripe, https://jobs.lever.co/palantir, https://jobs.ashbyhq.com/ramp, https://jobs.smartrecruiters.com/BoschGroup, https://apply.workable.com/blueground, https://bunq.recruitee.com, https://holidu.jobs.personio.de, https://career.teamtailor.com), an `ats:slug` pair (greenhouse:stripe, lever:palantir, ashby:ramp, smartrecruiters:BoschGroup, workable:blueground, recruitee:bunq, personio:holidu, teamtailor:career.teamtailor.com, lever-eu:slug), or a bare company name (stripe). A bare name is auto-detected by trying the eight systems in a fixed order until one lists jobs; if none does you get one free `not_found` row for that company.

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

Keep a job when its title contains at least one of these words or phrases (case-insensitive), e.g. `engineer`, `account executive`. Leave empty for every job.

## `locationContains` (type: `string`):

Keep a job when its location, any of its extra locations or its country contains this text (case-insensitive), e.g. `Berlin`, `United States`, `GB`.

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

Keep only postings the ATS marks as remote (or, where the ATS has no remote flag, whose location says Remote).

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

Keep only jobs published in the last N days. Uses the posting's published date, or its updated date when the ATS gives no published date. Leave empty for all.

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

Stop after this many rows have been saved. Each saved row with status `ok` is one billable result. The default of 20 keeps a first run cheap; raise it for production runs (a large board has 500 to 5,000 jobs).

## `maxItemsPerCompany` (type: `integer`):

Take at most this many jobs from each company, so one large board cannot use up the whole run. Leave empty for no per-company cap. The prefilled 7 makes the default run show three different systems.

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

Add the full advert to every row as `descriptionText` (plain text, HTML removed, e-mail addresses and phone numbers masked). Turn off for a lighter, faster run; the price per row does not change. SmartRecruiters needs one extra request per job for the text, made only for jobs that passed your filters and caps.

## `startUrls` (type: `array`):

Same meaning and same accepted forms as Companies; both lists are merged and de-duplicated. When this list still holds its default while Companies was changed (or the other way round), the default list is ignored.

## `proxyConfiguration` (type: `object`):

Optional and off by default. The eight systems answer plain HTTP requests from Apify's datacenter IPs, so no proxy is needed; enable the datacenter group only if you see `error` rows. Residential proxies are not required.

## Actor input object example

```json
{
  "companies": [
    "greenhouse:duolingo",
    "ashby:linear",
    "lever:spotify"
  ],
  "remoteOnly": false,
  "maxItems": 20,
  "maxItemsPerCompany": 7,
  "includeDescription": true,
  "startUrls": [
    "https://job-boards.greenhouse.io/duolingo",
    "https://jobs.ashbyhq.com/linear",
    "https://jobs.lever.co/spotify"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

# 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": [
        "greenhouse:duolingo",
        "ashby:linear",
        "lever:spotify"
    ],
    "maxItems": 20,
    "maxItemsPerCompany": 7,
    "includeDescription": true,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("glasswing/ats-jobs-scraper").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": [
        "greenhouse:duolingo",
        "ashby:linear",
        "lever:spotify",
    ],
    "maxItems": 20,
    "maxItemsPerCompany": 7,
    "includeDescription": True,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("glasswing/ats-jobs-scraper").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": [
    "greenhouse:duolingo",
    "ashby:linear",
    "lever:spotify"
  ],
  "maxItems": 20,
  "maxItemsPerCompany": 7,
  "includeDescription": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call glasswing/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

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

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/Rr7PbggWHtq1tNoLU/builds/1aVVzIgzVEhexBdAz/openapi.json
