# ATS Jobs API: Greenhouse, Lever, Ashby & More (`foresighted_sunset/ats-jobs-api`) Actor

Live open jobs straight from 7 ATS public job-board APIs (Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Personio). Normalized schema, salary from structured fields, and new/closed job tracking between runs. No scraping, no proxies.

- **URL**: https://apify.com/foresighted_sunset/ats-jobs-api.md
- **Developed by:** [Dano Desk](https://apify.com/foresighted_sunset) (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.50 / 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.

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 & More (+ Hiring Velocity, Ghost-Job Score & Company Enrichment)

Get **every open job of the companies you choose**, live from the employer's own applicant tracking system (ATS). Each run reads the **official public job-board APIs** that ATS vendors publish so employers' jobs can be shown and syndicated. There's no HTML scraping, no proxies and no stale cache: change the input and the output changes.

**Supported ATS (7):** Greenhouse · Lever (global + EU) · Ashby · SmartRecruiters · Workable · Recruitee · Personio

### Why this Actor

- **Live, not cached.** Every run calls each company's board API directly, so the results reflect the board at that moment.
- **One clean schema across 7 ATS:** title, company, locations, ISO countries, remote flag and workplace type, department/team, normalized employment type, salary, posted/updated dates, job and apply URLs, and the description as text and/or HTML.
- **Salary you can trust.** It uses structured pay data where the ATS provides it (Ashby compensation, Lever salaryRange, Recruitee salary, and Greenhouse pay ranges when enabled). Otherwise a conservative parser pulls a clear currency range from the description, and **the matched text is always attached** (`salary.text`, `salary.source`). There are no AI-guessed fields.
- **Hiring velocity mode.** Each job's first-seen and last-seen times are remembered in a named key-value store. Every run flags **new** jobs and emits **closed** ones, and OUTPUT gets per-company `opened / closed / net` counts and new roles by department. Schedule it daily or weekly for sales triggers, investor signals, or job alerts.
- **Ghost-job score on every job.** A transparent 0-100 risk score with the exact signals and points behind it (age, posted-date refresh, repost of a role that just closed, talent-pool wording, duplicate postings, no pay range in a pay-transparency state, thin description, stagnant board). Filter with `maxGhostScore`.
- **Company-level enrichment.** Feed it a dataset from any other Actor (Google Maps, lead lists, company lists): each row's website is checked for a supported ATS and you get one hiring-signal row per company: open jobs, posted in the last 30 days, departments, locations, remote and salary share, salary span, median days open, ghost counts, and new/closed since the last run.
- **One bad board never fails the run.** Each company is handled separately, and errors are reported per company with a reason (`BOARD_NOT_FOUND`, `ATS_NOT_DETECTED`, HTTP status…).
- **Polite by design.** Requests are paced per host (≥250 ms apart, max 2 in parallel per ATS), retried with exponential backoff on 429/5xx (honoring `Retry-After`), and only GET endpoints for published jobs are called. Application and candidate endpoints are never touched.

### Input

Add companies one per line, in any of these forms:

| Form | Example |
|---|---|
| Careers URL (auto-detected) | `https://jobs.lever.co/spotify`, `https://boards.greenhouse.io/airbnb`, `https://jobs.ashbyhq.com/ramp`, `https://apply.workable.com/skroutz`, `https://bunq.recruitee.com`, `https://ottonova.jobs.personio.de`, `https://jobs.smartrecruiters.com/BoschGroup` |
| `ats:token` | `greenhouse:stripe`, `lever:eu/acme`, `ashby:ramp`, `smartrecruiters:BoschGroup`, `workable:skroutz`, `recruitee:bunq`, `personio:ottonova` |
| Bare token (probed across all 7) | `stripe` |
| Custom careers page | `https://careers.acme.com/jobs`: fetched once (robots.txt respected) to find a Greenhouse, Lever, Ashby or other embed |
| Name override | `lever:spotify\|Spotify` (Lever and Ashby APIs don't return a company name) |

**Filters** (applied after fetching, so tracking still sees the full board): keywords (title, optionally description), exclude keywords, locations or countries (`remote` accepted), remote only, workplace types, departments/teams, employment types, and posted after (`2026-10-01` or `7 days`).

**Output modes:** `all` open jobs (plus closed since last run), only `new`, or `changes` (new + closed).

```json
{
  "companies": ["greenhouse:airbnb", "https://jobs.lever.co/spotify|Spotify", "https://jobs.ashbyhq.com/ramp"],
  "keywords": ["engineer"],
  "locations": ["US", "remote"],
  "postedAfter": "14 days",
  "outputMode": "all",
  "descriptionFormat": "text",
  "maxJobsPerCompany": 50
}
```

### Output (job level: one item per job)

```json
{
  "jobId": "ashby:ramp:0d52b853-…",
  "ats": "ashby", "atsJobId": "0d52b853-…", "companyToken": "ramp", "company": "ramp",
  "title": "Lead Recruiter | Executive",
  "location": "New York, NY (HQ)", "locations": ["New York, NY (HQ)", "San Francisco, CA", "Remote (US)"], "countries": ["US"],
  "remote": false, "workplaceType": "hybrid", "department": "People & Talent", "team": "People & Talent",
  "employmentType": "full_time", "employmentTypeRaw": "FullTime",
  "salary": { "min": 180000, "max": 250000, "currency": "USD", "period": "year", "text": "$180K - $250K", "source": "ats_structured" },
  "postedAt": "2026-10-19T17:06:15.365Z", "updatedAt": null,
  "url": "https://jobs.ashbyhq.com/ramp/0d52b853-…", "applyUrl": "https://jobs.ashbyhq.com/ramp/0d52b853-…/application",
  "descriptionText": "ABOUT RAMP …",
  "status": "open", "isNew": true, "firstSeenAt": "…", "lastSeenAt": "…", "daysOpen": 1, "scrapedAt": "…"
}
```

Closed jobs come through as `{ "status": "closed", "closedAt", "firstSeenAt", "lastSeenAt", "daysOpen", "title", "url", ... }`.

Dataset views: **Jobs**, **New & closed**, and **Salaries**. The `OUTPUT` record holds the run summary: per company, the status, open/matched/output counts, new/closed counts, velocity, and any error with its reason. It also has totals by ATS and the HTTP request count.

### Ghost-job score

Every open job gets `ghostScore` (0-100), `ghostRisk` (`low` < 25, `medium` 25-49, `high` ≥ 50), `effectiveAgeDays` and `ghostSignals`. A real example (Airbnb on Greenhouse, 2026-10-20):

```json
"ghostScore": 50, "ghostRisk": "high", "effectiveAgeDays": 223,
"ghostSignals": [
  { "code": "very_old_posting", "points": 35, "detail": "open ~223 days" },
  { "code": "evergreen_language", "points": 15, "detail": "description mentions a talent pool / future openings" }
]
```

| Signal | Points | Based on |
|---|---|---|
| `aging_posting` / `old_posting` / `very_old_posting` | 8 (45 d), 15 (60 d), 25 (90 d), 35 (180 d) | Earlier of the ATS posted date and our own first-seen date |
| `not_updated` | 10 | ATS updated date ≥ 90 days ago on a ≥ 90-day-old job |
| `date_refreshed` | 15 | The ATS posted date moved ≥ 7 days after we first saw the job (needs tracking) |
| `reposted` | 20 | A new job id with the same title + location as a job that closed on this board in the last 120 days (needs tracking) |
| `duplicate_postings` | 8 | 3+ open postings with the exact same title + location |
| `evergreen_title` / `evergreen_language` | 30 / 15 | Talent pool, general/open application, future openings, "always hiring" wording |
| `no_pay_range_in_pay_transparency_state` | 10 | No structured or clearly stated pay range, and a location in a US state/DC whose posting law requires one (CA, CO, CT, DC, HI, IL, MA, MD, ME, MN, NJ, NY, VT, VA, WA as of Oct 2026) |
| `thin_description` | 5 | Description under 300 characters |
| `board_stagnant` | 10 | No new job on the board for 45+ days of tracking |

This is a **heuristic risk indicator, not a finding** that a job is fake, and it is not legal advice (pay-law thresholds and remote-role rules vary). Signals that need history only start after the second tracked run. SmartRecruiters jobs skip the description and pay signals because the list API has no description.

### Company level and dataset enrichment

Set **Output level = companies** for one row per company, or point **Enrich a dataset** at another Actor's dataset (it defaults to company level). Each row's website, domain or careers URL is found automatically (`careersUrl`, `website`, `domain`, `url`, …) or from **Company field(s)**.

- ATS links and URLs are read directly. For a plain website, the homepage is fetched (robots.txt respected), then up to 2 careers links on the same site (or `/careers` and `/jobs`). That's at most 3 page fetches per company, and each company is looked up once even if it appears on many rows.
- Companies with no supported ATS are listed in `OUTPUT` and left out of the dataset unless **Output rows with no ATS found** is on.
- **Fields to copy from each row** go into `inputRow`, so you can join results back to the source dataset.

```json
{
  "inputValue": "https://www.figma.com", "inputField": "website", "inputRow": { "title": "Figma", "website": "https://www.figma.com" },
  "status": "ok", "company": "Figma", "ats": "greenhouse", "token": "figma", "boardUrl": "https://boards.greenhouse.io/figma",
  "careersPageUrl": "https://www.figma.com/careers/", "detectedBy": "careers_html",
  "isHiring": true, "openJobs": 141, "matchedJobs": 141, "postedLast30Days": 37, "medianDaysOpen": 88,
  "remoteShare": 0, "withSalaryShare": 0.652, "salarySpan": { "currency": "USD", "period": "year", "jobs": 83, "lowest": 70500, "highest": 376000, "medianMin": 155500 },
  "departments": [{ "name": "Sales", "count": 45 }, { "name": "Engineering", "count": 26 }, ...], "locations": [...], "countries": [...], "employmentTypes": [...], "newestTitles": [...],
  "ghost": { "high": 0, "medium": 69, "low": 72, "avgScore": 20, "topSignals": [{ "name": "very_old_posting", "count": 45 }, ...] },
  "newJobs": null, "closedJobs": null, "netChange": null, "trackingNote": "tracking off", "checkedAt": "…"
}
```

(Real output from a local test on 2026-10-20 with tracking off, shortened. With tracking on, `newJobs`/`closedJobs`/`netChange` fill in from the second run.) Filters (keywords, locations…) narrow the stats to matching jobs, while `openJobs` stays the full board count.

### How tracking works

- On the **first run** for a company, a baseline is saved and nothing is flagged new, so you never get "500 new jobs" on day one.
- On later runs, a job not seen before is `isNew: true`. A job that disappeared becomes a `closed` item and is removed from state.
- State is per board in the named KV store (`ats-jobs-tracker` by default). Use different store names for separate watchlists.
- If a board couldn't be fully fetched (very large SmartRecruiters boards beyond the page cap), closed-job detection is skipped for it. A missing page never produces false "closed" jobs.

### Sources and terms

| ATS | Endpoint | Documentation |
|---|---|---|
| Greenhouse | `boards-api.greenhouse.io/v1/boards/{token}/jobs?content=true` | Job Board API: "Job Board data is publicly available, so authentication is not required for any GET endpoints." |
| Lever | `api.lever.co/v0/postings/{site}?mode=json` (EU: `api.eu.lever.co`) | Postings API: "all job postings in the published state are publicly viewable. These jobs may be scraped by third parties." |
| Ashby | `api.ashbyhq.com/posting-api/job-board/{name}?includeCompensation=true` | Public Job Posting API |
| SmartRecruiters | `api.smartrecruiters.com/v1/companies/{id}/postings` (+ `/postings/{id}` for descriptions) | Posting API, listed under "No authentication… public data" |
| Workable | `apply.workable.com/api/v1/widget/accounts/{subdomain}?details=true` | Public published-jobs endpoint documented in Workable Help ("Using the Workable API to create a careers page") |
| Recruitee | `{company}.recruitee.com/api/offers/` | Careers Site API |
| Personio | `{company}.jobs.personio.de/xml` | Recruiting API: "Retrieving open positions" XML feed |

Only published job data is read. No personal data is collected, and nothing is ever posted to these APIs. In enrichment mode, company websites are read politely: robots.txt is checked for every page, at most 3 pages per company, with an identifying user agent.

### Empty input

With no companies and no dataset, the Actor runs a small read-only demo (Airbnb on Greenhouse, Spotify on Lever, Ramp on Ashby; 5 jobs each, tracking off) so a test run always returns data.

### Limits

- Website discovery only finds boards linked or embedded in the HTML. Pages that build their jobs list with JavaScript from an unknown source, and Workday/iCIMS/Taleo/SuccessFactors, report `ATS_NOT_DETECTED`.
- The ghost-job score is a heuristic from public posting data and our own history. Treat `high` as "check before applying or reaching out", not as proof.
- Lever and Ashby APIs don't return a company name. `company` falls back to the token unless you add `|Name`.
- SmartRecruiters returns an empty list (not 404) for unknown company IDs, and IDs are case-sensitive. Those entries show `status: "empty"` with a hint.
- SmartRecruiters descriptions need one extra request per output job. Large SmartRecruiters boards (5,000+ jobs) take ~50 requests to list.
- Custom careers pages that load jobs with JavaScript only, or that use other systems (Workday, iCIMS, Taleo, SuccessFactors…), can't be auto-detected. Pass the ATS token instead.
- Personio and Workable don't expose an updated date; Lever, Ashby, SmartRecruiters, Workable and Personio expose posted (or created) dates.
- Description salary parsing only accepts clear currency-marked ranges. It's a hint, so check `salary.source`.

# Actor input Schema

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

One per line. Careers URLs are auto-detected (https://jobs.lever.co/spotify, https://boards.greenhouse.io/airbnb, https://jobs.ashbyhq.com/ramp, https://apply.workable.com/skroutz, https://bunq.recruitee.com, https://ottonova.jobs.personio.de, https://jobs.smartrecruiters.com/BoschGroup). Or use ats:token (greenhouse:stripe, lever:eu/acme, ashby:ramp, smartrecruiters:BoschGroup, workable:skroutz, recruitee:bunq, personio:ottonova). A bare token (stripe) is probed across all supported ATS. A company website or custom careers page (acme.com, careers.acme.com) is fetched politely (robots.txt respected) to find the ATS embed, following up to 2 careers links. Add |Name to set the company name: lever:spotify|Spotify. Leave empty and set a dataset below to enrich rows from another Actor. With no companies and no dataset, a 3-company demo runs.

## `outputLevel` (type: `string`):

"companies" gives one hiring-signal row per company: open jobs, posted in last 30 days, departments, locations, remote and salary share, salary span, median days open, ghost-job counts, and new/closed since last run. Defaults to "companies" when a dataset is enriched, otherwise "jobs".

## `datasetId` (type: `string`):

ID of an Apify dataset whose rows contain a company website, domain or careers URL (e.g. the output of a Google Maps, lead or company-list Actor). Each row gets the company's ATS and hiring signals. Rows without a usable field are skipped and counted in OUTPUT.

## `companyFields` (type: `array`):

Field(s) holding the website, domain or careers URL, checked in order (dot paths allowed, e.g. company.website). Empty = auto: careersUrl, jobsUrl, website, websiteUrl, companyWebsite, domain, url…

## `passthroughFields` (type: `array`):

Copied into inputRow on each output row so you can join results back. Empty = the field used plus name/title/id/placeId/domain/website when present.

## `enrichItems` (type: `array`):

Instead of a dataset, paste rows directly, e.g. \[{"name":"Acme","website":"https://acme.com"}].

## `maxEnrichRows` (type: `integer`):

Rows read from the dataset. Duplicate companies are looked up once.

## `includeNotFoundRows` (type: `boolean`):

Company level only. When off, companies with no supported ATS are listed in OUTPUT only (not in the dataset).

## `followCareersLinks` (type: `boolean`):

When a website has no ATS embed, open up to 2 careers/jobs links on the same site (or /careers and /jobs). robots.txt is respected for every page.

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

Keep jobs whose title contains any of these (case-insensitive).

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

Search the job description too (SmartRecruiters descriptions are not available for filtering).

## `excludeKeywords` (type: `array`):

Drop jobs whose title contains any of these, e.g. senior, intern.

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

Substring match on locations or ISO country codes, e.g. Berlin, Germany, US. Use "remote" to also accept remote jobs.

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

Keep only jobs marked remote by the ATS or with Remote in their location.

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

Any of: remote, hybrid, onsite, unknown.

## `departments` (type: `array`):

Substring match on department or team, e.g. Engineering, Sales.

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

Any of: full_time, part_time, contract, intern, temporary, other, unknown.

## `postedAfter` (type: `string`):

A date (2026-10-01) or relative (24 hours, 7 days, 2 weeks, 1 month). Uses the posted date, or the updated date when the ATS does not expose one.

## `maxGhostScore` (type: `integer`):

Drop jobs whose ghost-job score is above this (e.g. 49 keeps low and medium risk). 0 = keep all.

## `outputMode` (type: `string`):

"new" and "changes" need tracking. On the first run for a company a baseline is saved and nothing is flagged new.

## `trackChanges` (type: `boolean`):

Remember first-seen and last-seen per job in a named key-value store, so each run flags new and closed jobs and reports per-company opened/closed/net counts in OUTPUT.

## `includeClosedJobs` (type: `boolean`):

Add a status=closed item for each job that disappeared since the last run.

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

Named key-value store that keeps tracking state. Use different names for independent watchlists.

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

SmartRecruiters needs one extra request per output job for descriptions; choose None to skip.

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

Cap on output jobs per company (newest first). 0 = no cap. Tracking always uses the full board.

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

Cap on total output jobs. 0 = no cap.

## `ghostScore` (type: `boolean`):

Score every open job 0-100 for ghost/stale risk from transparent signals (age, posted-date refresh, repost of a closed role, evergreen/talent-pool wording, duplicate postings, no pay range in a pay-transparency state, thin description, stagnant board). Each job lists the signals and points. A heuristic, not proof.

## `parseSalaryFromText` (type: `boolean`):

When the ATS has no structured salary, extract a clear currency range from the description (e.g. "$120,000 - $150,000"). The matched text is kept in salary.text.

## `greenhouseSalaries` (type: `boolean`):

One extra request per output Greenhouse job to read pay_input_ranges (structured pay transparency). Slower on big boards.

## `personioLanguage` (type: `string`):

Optional language code for Personio feeds, e.g. en or de.

## `smartRecruitersMaxPages` (type: `integer`):

Large employers can have thousands of postings. If a board is not fully fetched, closed-job detection is skipped for it.

## `probeBareTokens` (type: `boolean`):

For entries like "stripe", try each supported ATS until a board is found (up to 7 requests).

## `maxConcurrency` (type: `integer`):

How many company boards are processed at the same time.

## `perHostConcurrency` (type: `integer`):

Keeps load on each ATS API low.

## `minRequestGapMs` (type: `integer`):

Polite pacing per ATS host.

## `maxRetries` (type: `integer`):

Retries on 429, 5xx and network errors with exponential backoff (honors Retry-After).

## Actor input object example

```json
{
  "companies": [
    "greenhouse:airbnb",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp"
  ],
  "maxEnrichRows": 1000,
  "includeNotFoundRows": false,
  "followCareersLinks": true,
  "keywordsInDescription": false,
  "remoteOnly": false,
  "maxGhostScore": 0,
  "outputMode": "all",
  "trackChanges": true,
  "includeClosedJobs": true,
  "stateStoreName": "ats-jobs-tracker",
  "descriptionFormat": "text",
  "maxJobsPerCompany": 100,
  "maxJobs": 0,
  "ghostScore": true,
  "parseSalaryFromText": true,
  "greenhouseSalaries": false,
  "smartRecruitersMaxPages": 50,
  "probeBareTokens": true,
  "maxConcurrency": 5,
  "perHostConcurrency": 2,
  "minRequestGapMs": 250,
  "maxRetries": 4
}
```

# Actor output Schema

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

No description

## `summary` (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:airbnb",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("foresighted_sunset/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": [
        "greenhouse:airbnb",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("foresighted_sunset/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": [
    "greenhouse:airbnb",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp"
  ]
}' |
apify call foresighted_sunset/ats-jobs-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,foresighted_sunset/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/l5uGsaNdDv6IbT7LF/builds/oa6m8lAPqLUiGGZuO/openapi.json
