# ATS Job Scraper — Greenhouse, Lever, Ashby, Workday (`ultramarine_freezer/ats-job-scraper`) Actor

Scrape jobs from company career pages across Greenhouse, Lever, Ashby, Workday and SmartRecruiters. Unified schema, deduplication, delta mode and salary data. No cookies, no proxies, no login.

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

## Pricing

Pay per event

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/platform/actors/running/actors-in-store#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

## ATS Job Scraper — Greenhouse, Lever, Ashby, Workday & SmartRecruiters

**Scrape job listings straight from company career pages across five applicant tracking systems, in one run and one unified schema.** Give it a list of company domains — or pick a curated preset — and it works out which ATS each company uses, pulls every open role, and returns 46 clean, deduplicated, enriched fields per job.

No cookies. No logins. No proxies needed. No LinkedIn rate limits.

**One HTTP request to Databricks returns 819 jobs. A 57-job run finishes in under 4 seconds.**

***

### Start in one click

Pick a preset and run it. No slugs to research, no configuration:

```json
{ "presetLists": ["yc-and-ai"] }
```

| Preset | Companies | Open jobs |
|---|---|---|
| `yc-and-ai` | 15 | ~2,500 |
| `fintech` | 18 | ~2,300 |
| `data-infra` | 13 | ~2,400 |
| `devtools` | 17 | ~1,300 |
| `mobility-logistics` | 9 | ~1,400 |
| `ecommerce-retail` | 5 | ~1,200 |
| `security` | 10 | ~1,000 |
| `bigtech-startups` | 10 | ~900 |
| `enterprise-saas` | 10 | ~760 |
| `gaming-media` | 8 | ~570 |
| `healthtech` | 3 | ~110 |

Every company in every list was probed live against its ATS and confirmed to return open jobs.

***

### Why scrape the ATS instead of a job board

Most job scrapers hit aggregators — LinkedIn, Indeed, Glassdoor — where listings are stale, duplicated three times over, and wrapped in anti-bot defences that break weekly.

This one goes to the source: the company's own applicant tracking system. When a recruiter opens a role, it appears here first.

- **Fresher.** You see the posting the day it goes live, not when an aggregator indexes it.
- **No anti-bot fight.** These are public, documented APIs. Runs finish in seconds and don't silently degrade.
- **Real salary numbers.** Ashby and Lever publish structured pay ranges. You get numbers, not a string to parse.
- **The apply link is the real one.** Straight to the company's form, no redirect chain.

***

### Four ways to say what you want

```json
// 1. Curated preset — zero research
{ "presetLists": ["fintech", "devtools"] }

// 2. Company domains — the ATS is detected automatically
{ "companies": ["stripe.com", "openai.com", "databricks.com"] }

// 3. Career page URLs — pasted straight from the browser
{ "boardUrls": ["https://boards.greenhouse.io/figma", "https://jobs.lever.co/shieldai"] }

// 4. Exact boards, when you already know them
{ "boards": [{ "ats": "ashby", "slug": "openai" },
             { "ats": "workday", "tenant": "nvidia", "site": "NVIDIAExternalCareerSite" }] }
```

Mix them freely in one run. Duplicate boards are collapsed automatically.

***

### What makes this one different

#### Remote flags you can actually trust

Ashby marks hybrid roles as `isRemote: true`. Any scraper that copies that flag reports **480 remote jobs at OpenAI — 448 of which are hybrid roles in San Francisco.**

This Actor derives `is_remote` from the workplace type and location text, and exposes `workplace_type` as a clean `remote` / `hybrid` / `onsite`. Same company, same run: **37 genuinely remote roles**. When you filter with `remoteOnly`, you get remote jobs.

#### Derived fields that don't invent things

Every job is enriched with rule-based extraction — no LLM, no per-job cost, no hallucinated salaries:

| Field | What it gives you |
|---|---|
| `seniority` | intern / entry / mid / senior / lead / director / executive |
| `years_experience_min` | Lowest explicitly stated requirement |
| `skills` | Detected from a 150-technology taxonomy |
| `benefits` | equity, retirement, health insurance, PTO, parental leave, relocation, learning budget, 4-day week… |
| `education_level` | phd / master / bachelor / associate / high\_school |
| `visa_sponsorship` | `true`, `false`, or `null` when the posting says nothing |
| `requires_security_clearance` | Boolean |
| `is_agency` | Flags staffing and agency listings so you can exclude them |
| `salary_annual_min_usd` | Every salary normalised to annual USD, comparable across 40 currencies |
| `country_code`, `continent` | ISO-3166 codes derived even when the ATS only wrote a city |

**These are read from the title, never guessed from body noise.** A posting that mentions reporting to a "Senior Manager" is not a lead role; a Research Engineer asked to "partner with researchers" is not an executive. Where the title is silent, seniority falls back to the stated years of experience, and otherwise returns `null`. An honest null beats a confident wrong answer.

The same discipline applies everywhere: `Excel` is only detected as a skill when it appears as a tool, not in "a culture where everyone can excel". `Go` is not detected from "go-to-market". Salary is only read from description text when a currency symbol sits next to the numbers.

#### Delta mode — pay only for what changed

Set `deltaMode: true` and the Actor remembers what it saw last time. The next run returns only jobs that are **new**, **updated** or **removed**, tagged in `delta_status`.

| Run | Result | Billed |
|---|---|---|
| 1 — baseline | 27 jobs | 27 |
| 2 — nothing changed | **0 jobs** | **0** |
| 3 — 3 posted, 2 edited | 5 jobs | 5 |

A daily watch over 500 companies returns the twelve postings that moved, not the 40,000 rows you already had. Removed jobs are never charged.

#### Deduplication that works

ATS boards publish the same role once per office — "Senior Engineer (Berlin)", "Senior Engineer (London)", "Senior Engineer (R4953)". The Actor normalises titles, stripping location suffixes, requisition numbers and bracketed noise, and keeps the richest copy. Every record carries a stable `dedup_key` so you can dedupe further against your own sources.

#### Alerts built in

Point `slackWebhookUrl`, `discordWebhookUrl` or your own `webhookUrl` at the run and get a digest when it finishes. Pair with delta mode and a daily schedule for a job alert that only fires on real changes.

***

### Filters

**Text** — `searchText`, `titleKeywords`, `excludeTitleKeywords`, `excludeKeywords`, `departments`, `skills`
**Location** — `locations`, `excludeLocations`, `countryCodes`, `remoteOnly`, `workplaceTypes`
**Attributes** — `seniorityLevels`, `employmentTypes`, `maxAgeDays`, `maxYearsExperience`, `visaSponsorshipOnly`, `excludeAgencies`, `excludeCompanies`
**Salary** — `requireSalary`, `minSalaryUsd`, `maxSalaryUsd` (all normalised to annual USD first, so a €65/hour contract and a £90k salary sort correctly against each other)

A concrete example — senior backend roles in Europe paying over $120k, no agencies, posted this week:

```json
{
  "presetLists": ["devtools", "data-infra"],
  "titleKeywords": ["backend", "platform", "infrastructure"],
  "excludeTitleKeywords": ["intern", "manager"],
  "seniorityLevels": ["senior", "lead"],
  "countryCodes": ["GB", "DE", "NL", "ES", "PL"],
  "minSalaryUsd": 120000,
  "excludeAgencies": true,
  "maxAgeDays": 7
}
```

***

### Output

46 fields per job, identical whichever ATS produced them, in a fixed column order so your CSV never shifts.

```json
{
  "job_id": "2233810f44e4d892208708b0a40f904b",
  "dedup_key": "369a657675e0fdb099258b220f3a32b9",
  "title": "Android Engineer III",
  "seniority": "senior",
  "department": "Hinge",
  "company_name": "Match Group",
  "is_agency": false,
  "location_raw": "New York, New York",
  "location_city": "New York",
  "location_region": "New York",
  "country_code": "US",
  "continent": "North America",
  "is_remote": false,
  "workplace_type": "hybrid",
  "employment_type": "full_time",
  "years_experience_min": 5,
  "education_level": "bachelor",
  "skills": ["Kotlin", "Java", "Git", "Agile"],
  "benefits": ["health_insurance", "retirement", "equity", "paid_time_off"],
  "visa_sponsorship": null,
  "requires_security_clearance": false,
  "has_salary": true,
  "salary_min": 150000,
  "salary_max": 180000,
  "salary_currency": "USD",
  "salary_interval": "year",
  "salary_annual_min_usd": 150000,
  "salary_annual_max_usd": 180000,
  "salary_source": "ats",
  "posted_at": "2026-05-19T20:38:11.267Z",
  "scraped_at": "2026-08-08T04:15:06.701Z",
  "age_days": 80,
  "apply_url": "https://jobs.lever.co/matchgroup/3414ba28.../apply",
  "ats": "lever",
  "external_id": "3414ba28-35f7-45d3-8e13-35c883959635",
  "description_text": "…"
}
```

Three output profiles: **full**, **compact** (drops the HTML description) and **minimal** (17 core fields for dashboards). Five ready-made dataset views: overview, salary benchmarking, requirements & benefits, changes, remote roles.

***

### Coverage

| ATS | Targeting | Notes |
|---|---|---|
| **Greenhouse** | domain, slug, URL | Full descriptions, departments, offices |
| **Lever** | domain, slug, URL | Structured salary ranges, workplace type |
| **Ashby** | domain, slug, URL | Richest salary data of the five |
| **Workday** | URL, or tenant + site | Paginated; descriptions need one request per job |
| **SmartRecruiters** | domain, company ID, URL | Paginated, includes industry |

***

### What people use it for

- **Job boards and aggregators** — a listing site fed from company sources, fresher than the aggregators you'd otherwise scrape.
- **Recruiting intelligence** — track which teams a competitor is growing and where. Delta mode plus a Slack webhook turns this into a daily digest.
- **Salary benchmarking** — structured pay ranges across hundreds of companies, normalised to annual USD so markets are comparable.
- **Talent market research** — hiring velocity by department, seniority, location or required skill.
- **Staffing lead generation** — spot companies opening roles in your specialism the day they post.
- **Personal job hunting** — watch a preset list, filter to your seniority and salary floor, get only what's new each morning.

***

### Pricing

Pay per event. **No charge for starting a run.**

| Event | Price |
|---|---|
| Job scraped | **$0.002** |
| Company resolved to its ATS | $0.005 (first time only — cached afterwards) |

Every open role at Databricks — 819 jobs with full descriptions and enrichment — costs **$1.64**. Running the same watchlist daily in delta mode costs a few cents once the baseline is set.

Jobs reported as `removed` are free. Enrichment is free — it's rule-based, not an LLM call.

***

### Frequently asked questions

**Do I need a proxy?**
No. These are public APIs and the Actor runs fine on Apify's default IPs.

**Will it break when a site adds anti-bot protection?**
Far less often than a scraper that renders career pages in a browser. The Actor talks to documented JSON APIs these platforms maintain for their own job widgets.

**How fresh is the data?**
Fetched live at run time, straight from the ATS. Every record carries `scraped_at`, `posted_at` and `age_days`, so freshness is visible rather than assumed. This is not a cached index served back to you.

**Why isn't there salary data on every job?**
Because most companies don't publish one. Ashby and Lever expose structured ranges when the employer fills them in; Greenhouse and Workday rarely do. Where nothing structured exists, the Actor looks for an explicit range in the description — but only next to a currency symbol. Otherwise the field stays null instead of guessing.

**Why is `seniority` sometimes null?**
Because the title didn't say and the description didn't state years of experience. Guessing from body text produced measurably wrong answers in testing, so the Actor returns null instead.

**Can I scrape a company that isn't on these five systems?**
Not yet. Workable, Recruitee, Personio, Teamtailor and BambooHR are next. If a company you need is missing, open an issue and it'll be prioritised.

**Is scraping this data legal?**
The Actor collects publicly available job listings that companies publish deliberately for candidates to find, and no personal data. How you use scraped data remains your responsibility — Apify's [ethical scraping guide](https://blog.apify.com/is-web-scraping-legal/) is a good starting point.

***

### Integrations

Works with everything on the Apify platform: schedules, webhooks, the API, and integrations with Make, Zapier, Google Sheets, Slack, Airbyte and n8n. Built-in Slack, Discord and custom-webhook delivery means you often don't need a second tool at all.

### Support

Found a bug, need another ATS, want a field that isn't there? Open an issue on the Actor's Issues tab. Reports are answered within 24 hours.

# Actor input Schema

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

Company domains or names. The Actor automatically finds which ATS each company uses and scrapes its job board. Example: <code>stripe.com</code>, <code>databricks.com</code>, <code>openai</code>.

## `presetLists` (type: `array`):

Curated bundles of companies, each verified live against its ATS. Pick one and run — no slugs to look up.

## `boardUrls` (type: `array`):

Paste career page URLs and the Actor works out the ATS and identifier by itself. Example: <code>https://boards.greenhouse.io/figma</code> or <code>https://jobs.lever.co/shieldai</code>.

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

Scrape specific boards directly.<br><b>Greenhouse / Lever / Ashby / SmartRecruiters:</b> <code>{"ats":"greenhouse","slug":"stripe"}</code><br><b>Workday:</b> <code>{"ats":"workday","tenant":"nvidia","site":"NVIDIAExternalCareerSite"}</code>

## `searchText` (type: `string`):

Keep only jobs containing this text in the title or description.

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

Keep only jobs whose <b>title</b> contains at least one of these. More precise than a full-text search.

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

Drop jobs whose title contains any of these. Useful for stripping out <code>intern</code>, <code>manager</code> or a stack you don't want.

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

Drop jobs whose title or description contains any of these words.

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

Keep only jobs in these departments or teams (partial, case-insensitive match).

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

Keep only jobs where the Actor detected at least one of these skills in the posting. Example: <code>Python</code>, <code>Kubernetes</code>, <code>React</code>.

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

Keep only jobs matching any of these locations (partial, case-insensitive).

## `excludeLocations` (type: `array`):

Drop jobs matching any of these locations.

## `countryCodes` (type: `array`):

Keep only jobs in these countries, as ISO-3166 alpha-2 codes: <code>US</code>, <code>GB</code>, <code>DE</code>, <code>MX</code>. The Actor derives the code even when the ATS only wrote the country name.

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

Genuinely remote roles only. Hybrid is excluded — several ATS platforms flag hybrid roles as remote, and this Actor does not.

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

Keep only these workplace arrangements.

## `seniorityLevels` (type: `array`):

Keep only these seniority levels, detected from the job title and description.

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

Keep only these employment types.

## `maxAgeDays` (type: `integer`):

Drop jobs published or updated longer ago than this. Kills stale listings.

## `maxYearsExperience` (type: `integer`):

Keep only jobs asking for at most this many years. Jobs that don't state a requirement are kept.

## `visaSponsorshipOnly` (type: `boolean`):

Keep only jobs where the posting explicitly says sponsorship is available.

## `excludeAgencies` (type: `boolean`):

Drop postings that read as agency or staffing listings rather than direct employers.

## `excludeCompanies` (type: `array`):

Drop jobs from these companies (partial, case-insensitive match on the company name).

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

Keep only postings that publish a pay range.

## `minSalaryUsd` (type: `integer`):

Keep only jobs whose range reaches at least this amount, converted to annual USD. Jobs without salary data are dropped.

## `maxSalaryUsd` (type: `integer`):

Keep only jobs whose range starts at or below this amount, converted to annual USD.

## `outputProfile` (type: `string`):

<b>Full</b> — every field including the HTML description.<br><b>Compact</b> — drops the HTML description, keeps plain text.<br><b>Minimal</b> — 17 core fields, ideal for dashboards and spreadsheets.

## `enrichment` (type: `boolean`):

Adds seniority, skills, benefits, years of experience, education level, visa sponsorship, security clearance, agency detection and salary normalised to annual USD. Rule-based, so it costs nothing extra and never invents a value.

## `extractSalaryFromDescription` (type: `boolean`):

When the ATS gives no structured pay data, look for an explicit range in the description text. Requires a currency symbol next to the numbers, so it can't mistake other figures for a salary.

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

Fetch the complete description as HTML and plain text. For Workday and SmartRecruiters this needs one extra request per job. Turning it off also disables description-based enrichment.

## `includeCompensation` (type: `boolean`):

Extract structured salary ranges where the ATS exposes them. Ashby and Lever provide real numbers.

## `deduplicate` (type: `boolean`):

Drop repeated listings of the same role at the same company and location, which ATS boards commonly publish once per office.

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

Hard cap on jobs pushed to the dataset across all boards.

## `maxItemsPerBoard` (type: `integer`):

Hard cap per company. Useful when scraping many companies and you only want a sample of each.

## `deltaMode` (type: `boolean`):

Return only jobs that are new, changed or removed since the previous run. Ideal for daily monitoring: cheaper runs and no repeated data.

## `deltaStateKey` (type: `string`):

Name under which the comparison state is stored. Use different names to track several independent watchlists.

## `includeRemovedInDelta` (type: `boolean`):

In delta mode, also output jobs that disappeared since the last run, marked <code>removed</code>. Removed rows are never charged.

## `slackWebhookUrl` (type: `string`):

Post a digest of the jobs found to Slack when the run finishes.

## `discordWebhookUrl` (type: `string`):

Post a digest of the jobs found to Discord when the run finishes.

## `webhookUrl` (type: `string`):

POST a JSON body with the run summary and up to 100 jobs to your own endpoint.

## `webhookHeaders` (type: `object`):

Extra HTTP headers for the custom webhook, e.g. an authorization token.

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

Parallel requests. Lower it if a career site starts rate limiting.

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

ATS APIs are public and normally need no proxy. Enable one only if you hit rate limits from your own IP.

## Actor input object example

```json
{
  "companies": [
    "stripe.com",
    "databricks.com",
    "openai.com"
  ],
  "presetLists": [],
  "boardUrls": [],
  "boards": [],
  "searchText": "machine learning engineer",
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "excludeKeywords": [],
  "departments": [],
  "skills": [],
  "locations": [],
  "excludeLocations": [],
  "countryCodes": [],
  "remoteOnly": false,
  "workplaceTypes": [],
  "seniorityLevels": [],
  "employmentTypes": [],
  "visaSponsorshipOnly": false,
  "excludeAgencies": false,
  "excludeCompanies": [],
  "requireSalary": false,
  "outputProfile": "full",
  "enrichment": true,
  "extractSalaryFromDescription": true,
  "includeDescription": true,
  "includeCompensation": true,
  "deduplicate": true,
  "deltaMode": false,
  "deltaStateKey": "default",
  "includeRemovedInDelta": true,
  "webhookHeaders": {},
  "maxConcurrency": 10,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# 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.com",
        "databricks.com",
        "openai.com"
    ],
    "presetLists": [],
    "boardUrls": [],
    "boards": [],
    "titleKeywords": [],
    "excludeTitleKeywords": [],
    "excludeKeywords": [],
    "departments": [],
    "skills": [],
    "locations": [],
    "excludeLocations": [],
    "countryCodes": [],
    "workplaceTypes": [],
    "seniorityLevels": [],
    "employmentTypes": [],
    "excludeCompanies": [],
    "webhookHeaders": {}
};

// Run the Actor and wait for it to finish
const run = await client.actor("ultramarine_freezer/ats-job-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": [
        "stripe.com",
        "databricks.com",
        "openai.com",
    ],
    "presetLists": [],
    "boardUrls": [],
    "boards": [],
    "titleKeywords": [],
    "excludeTitleKeywords": [],
    "excludeKeywords": [],
    "departments": [],
    "skills": [],
    "locations": [],
    "excludeLocations": [],
    "countryCodes": [],
    "workplaceTypes": [],
    "seniorityLevels": [],
    "employmentTypes": [],
    "excludeCompanies": [],
    "webhookHeaders": {},
}

# Run the Actor and wait for it to finish
run = client.actor("ultramarine_freezer/ats-job-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": [
    "stripe.com",
    "databricks.com",
    "openai.com"
  ],
  "presetLists": [],
  "boardUrls": [],
  "boards": [],
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "excludeKeywords": [],
  "departments": [],
  "skills": [],
  "locations": [],
  "excludeLocations": [],
  "countryCodes": [],
  "workplaceTypes": [],
  "seniorityLevels": [],
  "employmentTypes": [],
  "excludeCompanies": [],
  "webhookHeaders": {}
}' |
apify call ultramarine_freezer/ats-job-scraper --silent --output-dataset

```

## MCP server setup

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