# ATS Salary, Skills & Seniority Intelligence (`johnatan029/ats-salary-skills-intelligence`) Actor

Turn public Greenhouse, Lever and Ashby job boards into structured salary, skills and seniority intelligence. Parse published pay ranges, detect skills with a versioned dictionary, normalize titles, filter jobs, and get free coverage and hiring summaries. No salary estimates or AI-generated fields.

- **URL**: https://apify.com/johnatan029/ats-salary-skills-intelligence.md
- **Developed by:** [Johnn Mottin](https://apify.com/johnatan029) (community)
- **Categories:** Jobs, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 enriched 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?

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

### Turn public ATS job boards into salary, skills & seniority intelligence

Read current jobs from **Greenhouse, Lever, and Ashby** and transform each posting into structured hiring intelligence.

This Actor keeps the source data honest:

- salary ranges are parsed only when the company publicly publishes compensation;
- skills are matched with a versioned deterministic dictionary;
- seniority is derived from job-title rules;
- missing salary or skills are not filled with guesses;
- there is no LLM or AI inference in runtime.

Each run is a **fresh snapshot of the current public board**. It is designed for salary research, talent intelligence, competitor hiring analysis, recruiting data, workforce research, and automated hiring-intelligence pipelines.

#### Key features

- **Greenhouse, Lever & Ashby public job boards**
- **ATS auto-detection from company slug**
- **Published salary range parsing**
- **Currency and pay-period extraction**
- **Versioned skills dictionary**
- **Deterministic seniority classification**
- **Normalized job titles**
- **Location, department and remote signals**
- **Skill filtering**
- **Seniority filtering**
- **Free hiring aggregations**
- **Honest salary coverage**
- **Honest skills coverage**
- **Free records when enrichment is empty**
- **No salary estimation**
- **No AI-generated skills**
- **No login**
- **No browser automation**
- **Pay Per Event**

> **Unofficial community Actor. Not affiliated with, sponsored by, or endorsed by Greenhouse, Lever, Ashby, or any company whose public job board is read.** Data comes from public ATS job-board endpoints and remains subject to the availability and policies of each source.

***

### What this Actor is for

A basic ATS scraper answers:

> What jobs are open?

This Actor is built to answer richer questions:

- What salary ranges are competitors publishing?
- Which technical and business skills appear most often?
- What seniority mix is a company hiring for?
- Which locations are active?
- Are competitors hiring staff/principal talent or mostly junior/mid-level roles?
- Which skills appear across a set of companies?
- How much of the current hiring board actually exposes salary information?

The output can support:

- salary benchmarking;
- talent intelligence;
- recruiting research;
- competitive intelligence;
- workforce planning research;
- hiring-signal analysis;
- market research;
- job-data products;
- dashboards;
- scheduled reports;
- n8n, Make, webhook and database workflows.

***

### Important: this is enrichment, not a change monitor

Every run reads the **current public job board** and enriches the jobs it finds.

It does not maintain a user-facing diff state for:

```text
NEW_JOB
JOB_CLOSED
JOB_CHANGED
```

If you need persistent hiring-change events, use the separate **ATS Hiring Signals Monitor**.

Use this Actor when you need:

```text
current job board
+
salary intelligence
+
skills intelligence
+
seniority intelligence
```

***

### Supported ATS platforms

The current implementation supports:

```text
Greenhouse
Lever
Ashby
```

The Actor uses their public job-board endpoints.

#### Greenhouse

The Actor requests job content because salary and skills often appear in the published description.

#### Lever

The Actor reads the public postings API and combines the available description/list content for enrichment.

#### Ashby

The Actor reads the public Ashby job-board API and can use structured compensation when Ashby exposes it.

***

### Who it's for

#### Recruiting and talent intelligence teams

Analyze:

- published salary bands;
- in-demand skills;
- seniority distribution;
- hiring locations;
- departments;
- competitor hiring mix.

***

#### Salary and compensation research

Use the publicly disclosed salary values without estimating missing compensation.

The Actor can structure:

```text
minimum pay
maximum pay
currency
period
raw published salary text
```

when the source exposes enough information.

***

#### B2B and competitive intelligence teams

Hiring can reveal:

- new departments;
- geographic expansion;
- technical investment;
- senior leadership hiring;
- shifts in skill demand.

This Actor enriches the current job inventory so those signals are easier to analyze downstream.

***

#### Data and automation teams

Connect the structured records to:

- n8n;
- Make;
- Google Sheets;
- databases;
- BI tools;
- CRM;
- webhooks;
- internal applications;
- AI agents downstream.

The Actor itself does not use an LLM to generate enrichment fields.

***

### How it works

For each company input, the Actor:

1. normalizes the company slug;
2. uses the requested ATS or auto-detects Greenhouse → Lever → Ashby;
3. fetches the public job board;
4. normalizes each posting;
5. parses published salary when enabled;
6. detects skills when enabled;
7. derives seniority from title rules;
8. creates a normalized title;
9. applies optional skill and seniority filters;
10. sends enriched records through the billable path;
11. sends zero-enrichment records through the free path;
12. builds free per-run aggregations;
13. writes a free `RUN_SUMMARY`;
14. stores operational diagnostics in `STATS`.

***

### Input

#### Recommended example

```json
{
  "companies": [
    {
      "slug": "spotify",
      "ats": "lever"
    },
    {
      "slug": "linear",
      "ats": "ashby"
    },
    {
      "slug": "gitlab",
      "ats": "greenhouse"
    }
  ],
  "includeSalary": true,
  "includeSkills": true,
  "skillsFilter": [],
  "seniorityFilter": [],
  "maxJobsPerCompany": 200,
  "maxResults": 1000,
  "maxRuntimeMs": 300000
}
```

#### Input fields

| Field | Default | Description |
|---|---:|---|
| `companies` | required | Company ATS slugs. Accepts a string or `{ slug, ats }`. Maximum 100 entries. |
| `includeSalary` | `true` | Parse publicly published compensation when detectable. |
| `includeSkills` | `true` | Match skills with the versioned deterministic dictionary. |
| `skillsFilter` | `[]` | Keep only jobs containing one of the selected skill keys. |
| `seniorityFilter` | `[]` | Keep only jobs matching selected seniority values. |
| `maxJobsPerCompany` | `200` | Maximum jobs read per company before enrichment. Range: 1–2,000. |
| `maxResults` | `1000` | Maximum billable enriched records. Range: 1–5,000. |
| `maxRuntimeMs` | `300000` | Runtime cap in milliseconds. |
| `debug` | `false` | Additional diagnostic logs. |

***

### Company input format

You can provide a bare slug:

```json
{
  "companies": [
    "spotify",
    "linear"
  ]
}
```

A bare slug uses:

```text
ats: auto
```

The Actor tries:

```text
greenhouse
lever
ashby
```

in that order.

For more control, specify the ATS:

```json
{
  "companies": [
    {
      "slug": "spotify",
      "ats": "lever"
    }
  ]
}
```

***

### How to find the ATS slug

The slug is the company identifier in the public ATS URL.

#### Greenhouse

Example:

```text
https://boards.greenhouse.io/gitlab
```

Slug:

```text
gitlab
```

#### Lever

Example:

```text
https://jobs.lever.co/spotify
```

Slug:

```text
spotify
```

#### Ashby

Example:

```text
https://jobs.ashbyhq.com/linear
```

Slug:

```text
linear
```

This Actor does not discover an ATS from a normal company homepage/domain.

***

### Salary intelligence

Salary parsing is deterministic.

The Actor never creates a salary because a role *should* pay a certain amount.

When compensation is publicly present, the output can contain:

```json
{
  "detected": true,
  "min": 107483,
  "max": 153547,
  "currency": "USD",
  "period": "year",
  "raw": "$107,483 –$153,547 USD"
}
```

When compensation is not detected:

```json
{
  "detected": false,
  "min": null,
  "max": null,
  "currency": null,
  "period": null,
  "raw": null
}
```

***

### Supported salary behavior

The parser handles common published ranges and single-value compensation patterns using deterministic rules.

Currencies/codes supported by the current parser include cases involving:

```text
USD
EUR
GBP
BRL
CAD
AUD
$
€
£
R$
```

Pay periods can be classified when terms such as:

```text
year
annual
month
week
day
hour
```

are available near the published amount.

#### Important

Salary coverage depends entirely on what the employer publishes.

A company that publishes no compensation can legitimately produce:

```text
salaryCoverage: 0
```

***

### Skills intelligence

The Actor uses a versioned dictionary.

Current records carry a value such as:

```text
ats-skills-2026-08.1
```

in:

```text
skillsVersion
```

This makes historical datasets easier to interpret when the dictionary evolves.

#### Skill categories

The current dictionary covers categories such as:

- programming languages;
- frameworks;
- databases/data tooling;
- cloud and infrastructure;
- engineering practices;
- design;
- analytics;
- business tooling.

Examples include:

```text
Python
Java
JavaScript
TypeScript
Go
Rust
Kotlin
C++
C#
React
Next.js
Vue
Angular
Node.js
Django
Spring
PostgreSQL
MongoDB
Redis
Kafka
Spark
Airflow
dbt
Snowflake
BigQuery
Kubernetes
Docker
Terraform
AWS
Google Cloud
Azure
Linux
Grafana
Datadog
GraphQL
Salesforce
HubSpot
Tableau
Looker
SEO
CRM
B2B
SaaS
```

The dictionary is finite.

A skill outside the current dictionary is not invented or inferred.

***

### Skill matching safety

Skill matching uses boundary-aware deterministic rules.

For example, a text containing:

```text
antitrust
```

must not be counted as:

```text
Rust
```

because the substring happens to contain the letters `rust`.

This type of false-positive protection is part of the dictionary design.

***

### Seniority intelligence

The Actor derives seniority from job-title rules.

Possible values include:

```text
intern
junior
mid
senior
staff
principal
lead
manager
director
vp
c-level
```

If no supported title signal is detected:

```text
seniority: null
```

The Actor does not guess seniority from the job description.

***

### Title normalization

The Actor also creates:

```text
titleNormalized
```

The normalization removes supported seniority/noise tokens and simplifies a title for grouping.

Example:

```text
Senior Android Engineer II (Remote)
```

can normalize to a cleaner title representation.

Normalization is deterministic.

***

### Filters

#### Skill filter

Example:

```json
{
  "skillsFilter": [
    "python",
    "kubernetes"
  ]
}
```

Only jobs with one of the requested dictionary keys remain in output.

#### Seniority filter

Example:

```json
{
  "seniorityFilter": [
    "staff",
    "principal",
    "director"
  ]
}
```

The filters narrow the output.

They do not change how the source board itself is fetched.

***

### Output

The default dataset contains:

```text
ENRICHED_JOB
RUN_SUMMARY
```

***

### `ENRICHED_JOB`

Every delivered current job is normalized to one schema.

#### Example

```json
{
  "recordType": "ENRICHED_JOB",
  "entityId": "ats:lever/spotify/1e33d918",
  "atsSource": "lever",
  "companySlug": "spotify",
  "jobId": "1e33d918",
  "title": "Android Engineer - Advertising",
  "titleNormalized": "android engineer",
  "url": "https://jobs.lever.co/spotify/...",
  "location": "New York, NY",
  "isRemote": null,
  "department": "Engineering",
  "employmentType": "Full-time",
  "publishedAt": null,
  "observedAt": "2026-08-16T22:10:44.107Z",
  "salary": {
    "detected": true,
    "min": 107483,
    "max": 153547,
    "currency": "USD",
    "period": "year",
    "raw": "$107,483 –$153,547 USD"
  },
  "skills": [
    {
      "key": "kotlin",
      "label": "Kotlin",
      "category": "language"
    },
    {
      "key": "agile",
      "label": "Agile",
      "category": "practice"
    }
  ],
  "skillsVersion": "ats-skills-2026-08.1",
  "seniority": null,
  "enrichmentEmpty": false
}
```

***

### Output fields

| Field | Description |
|---|---|
| `recordType` | `ENRICHED_JOB`. |
| `entityId` | Stable ATS/company/job identity. |
| `atsSource` | `greenhouse`, `lever`, or `ashby`. |
| `companySlug` | ATS board slug. |
| `jobId` | Source job ID. |
| `title` | Published title. |
| `titleNormalized` | Deterministically normalized title. |
| `url` | Public job/apply URL. |
| `location` | Published location. |
| `isRemote` | Remote flag when determinable. |
| `department` | Department/team when available. |
| `employmentType` | Employment/commitment type when available. |
| `publishedAt` | Source publish timestamp when available. |
| `observedAt` | Actor observation timestamp. |
| `salary` | Parsed published compensation object. |
| `skills` | Matched skills. |
| `skillsVersion` | Dictionary version. |
| `seniority` | Title-derived seniority or `null`. |
| `enrichmentEmpty` | Whether salary, skills and seniority are all empty. |

***

### Billing honesty: empty enrichment is free

A job can be useful even when the Actor does not detect:

```text
salary
skills
seniority
```

The job is still delivered.

When all three are empty:

```text
enrichmentEmpty: true
```

the record is pushed through the **free path**.

It is not charged as:

```text
enriched-job
```

You pay for enrichment, not for a copy of a public job row with no detected enrichment.

***

### What makes an enriched job billable

A record is considered enriched when at least one of these is present:

```text
published salary detected
one or more dictionary skills detected
seniority detected
```

If any of those signals exists:

```text
enrichmentEmpty: false
```

and the delivered record can be charged as:

```text
enriched-job
```

***

### `RUN_SUMMARY`

The final run summary is free.

It can include:

```text
billableRecords
enrichmentEmptyRecords
companiesNotFound
jobsProcessed
salaryCoverage
skillsCoverage
enrichmentEmptyCount
topSkills
bySeniority
byCompany
topLocations
skillsVersion
qualityAlert
warnings
cost
pricingLabel
```

This gives you an immediate view of the usefulness and coverage of the current run.

***

### Honest coverage metrics

#### Salary coverage

```text
salaryCoverage
```

is the percentage of delivered job records where a public salary was actually detected.

It is **not** an estimate of what missing roles probably pay.

#### Skills coverage

```text
skillsCoverage
```

is the percentage of delivered records with at least one dictionary skill match.

A low number can mean:

- sparse descriptions;
- jobs outside the current dictionary;
- source limitations.

***

### Free hiring aggregations

The free summary can include:

#### Top skills

```text
topSkills
```

#### Seniority mix

```text
bySeniority
```

#### Jobs by company

```text
byCompany
```

#### Top locations

```text
topLocations
```

These aggregations are based on the job records actually delivered in the run.

***

### Company not found

When a slug is not found on the requested ATS:

```text
NOT_FOUND
```

is recorded at the unit/run level.

One missing company does not automatically fail the whole batch.

With:

```text
ats: auto
```

the Actor tries each supported ATS before declaring the slug not found.

***

### Scheduling

This Actor is a snapshot transformation product.

Typical cadence:

```text
weekly
```

for competitor hiring intelligence.

You can also run it:

```text
on demand
daily
before a recruiting or compensation review
```

Because every run returns the current board, repeated schedules can contain the same current jobs.

If you need only new/changed hiring events, use the separate monitor product.

***

### Recommended scheduled workflow

1. Configure a set of competitor/company ATS slugs.
2. Save the input as an Apify **Task**.
3. Go to **Console → Schedules → Create schedule**.
4. Choose a cadence.
5. Send the enriched dataset to your destination.
6. Use `RUN_SUMMARY` for high-level hiring intelligence.

Possible destinations:

- Google Sheets;
- Slack;
- n8n;
- Make;
- webhook;
- database;
- BI dashboard;
- internal application.

***

### Example competitor hiring workflow

#### Input

```json
{
  "companies": [
    {
      "slug": "spotify",
      "ats": "lever"
    },
    {
      "slug": "linear",
      "ats": "ashby"
    }
  ],
  "includeSalary": true,
  "includeSkills": true,
  "maxJobsPerCompany": 500,
  "maxResults": 1000
}
```

#### Downstream use

1. Actor reads both public job boards.
2. Salary, skills and seniority are extracted deterministically.
3. Records are stored in the dataset.
4. Summary provides salary/skills coverage and hiring mix.
5. A downstream workflow can:
   - compare salary bands;
   - rank skills;
   - watch seniority mix;
   - populate a recruiting dashboard;
   - generate a management report;
   - pass structured records to an AI analysis step.

***

### API

Run the Actor through the Apify API:

```bash
curl -s "https://api.apify.com/v2/acts/<YOUR_USERNAME>~ats-salary-skills-intelligence/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "companies":[
      {"slug":"spotify","ats":"lever"},
      {"slug":"linear","ats":"ashby"}
    ],
    "includeSalary":true,
    "includeSkills":true,
    "maxResults":1000
  }'
```

Replace:

```text
<YOUR_USERNAME>
<YOUR_TOKEN>
```

with your Apify account values.

***

### Integrations

Use with:

- Apify API;
- Tasks;
- Schedules;
- webhooks;
- n8n;
- Make;
- Google Sheets;
- Slack;
- databases;
- BI tools;
- CRM;
- internal applications;
- AI agents downstream.

***

### Pricing

This Actor uses **Pay Per Event**.

The code uses two custom events:

```text
actor-start
enriched-job
```

#### `actor-start`

Charged once after valid input has been accepted.

Invalid input is rejected before the start charge is opened.

#### `enriched-job`

Charged per delivered `ENRICHED_JOB` where:

```text
enrichmentEmpty: false
```

A job with no detected salary, skills, or seniority is delivered for free.

The **Pricing** tab on the Actor page is always the authoritative source for current prices.

***

### Cost control

The main controls are:

```text
maxResults
maxJobsPerCompany
maxRuntimeMs
```

Output filters can also reduce billable results:

```text
skillsFilter
seniorityFilter
```

#### Smaller research run

Use:

- fewer companies;
- a lower `maxJobsPerCompany`;
- narrower skills/seniority filters;
- a smaller `maxResults`.

#### Larger market scan

Use:

- more companies;
- larger board caps;
- broad filters.

***

### Run health

Operational statistics are written to:

```text
STATS
```

in the default Key-Value Store.

Depending on the run, statistics can include:

- HTTP requests;
- retries;
- companies completed;
- companies failed;
- companies not found;
- billable records;
- free records;
- quality warnings;
- source availability;
- runtime;
- spend/cost metrics.

***

### Honest limits

#### Only Greenhouse, Lever and Ashby

The current implementation does not support:

- Workday;
- SmartRecruiters;
- Workable;
- Recruitee;
- Personio;
- custom career sites.

***

#### It needs the ATS slug

A normal company domain such as:

```text
example.com
```

is not automatically converted into an ATS board.

Provide the correct ATS slug or use a bare slug with `ats: auto`.

***

#### Auto-detection uses a fixed order

`ats: auto` tries:

```text
Greenhouse
Lever
Ashby
```

in that order.

If the same slug happens to be valid on multiple ATS platforms, the first successful board wins.

Specify `ats` explicitly when you need certainty.

***

#### Salary is never estimated

No public compensation means:

```text
salary.detected: false
```

The Actor does not infer salary from:

- title;
- location;
- seniority;
- market averages.

***

#### Salary text can vary

Compensation language is free text on some boards.

The parser handles supported patterns but cannot guarantee detection of every salary format a company might write.

***

#### The skills dictionary is finite

Skills outside the current version are not returned.

This is intentional: deterministic output is preferred over invisible model inference.

***

#### Seniority comes from the title

The Actor does not inspect the full description to guess seniority.

A title without a supported seniority token can return:

```text
seniority: null
```

***

#### This is not a change monitor

Repeated runs can return the same current jobs.

Use the separate ATS Hiring Signals Monitor for new/changed job monitoring.

***

#### Source behavior can change

Greenhouse, Lever or Ashby can change public API behavior or response contracts.

The Actor includes retries, health checks, and explicit contract errors to surface unexpected changes instead of inventing fields.

***

### FAQ

#### Do I need an ATS account?

No.

#### Do I need a Greenhouse, Lever or Ashby API key?

No for the public job-board endpoints used by this Actor.

#### Does it use a browser?

No.

#### Can it auto-detect the ATS?

Yes, from the company slug.

It does not discover a slug from a normal company website.

#### Does it return salaries?

Yes, when compensation is publicly published and the deterministic parser can detect it.

#### Does it estimate missing salaries?

No.

#### Does it use AI to infer skills?

No.

#### Does it use AI to infer seniority?

No.

#### Which seniority levels are supported?

```text
intern
junior
mid
senior
staff
principal
lead
manager
director
vp
c-level
```

#### Can I filter for Python jobs?

Yes.

```json
{
  "skillsFilter": [
    "python"
  ]
}
```

#### Can I filter senior roles?

Yes.

```json
{
  "seniorityFilter": [
    "senior",
    "staff",
    "principal"
  ]
}
```

#### What does `enrichmentEmpty: true` mean?

The job was valid, but the Actor detected no salary, no dictionary skill, and no seniority signal.

The row is still delivered and is not charged as `enriched-job`.

#### Are summary records charged?

No.

#### What is salary coverage?

The percentage of delivered records with a detected published salary.

#### What is skills coverage?

The percentage of delivered records with at least one dictionary skill.

#### Can I schedule the Actor?

Yes.

Weekly is a practical starting point for competitor hiring research.

#### Will a scheduled run return only new jobs?

No.

This Actor returns current-board snapshots.

#### What am I charged for?

The custom start event shown in Pricing plus delivered job records that contain at least one enrichment.

#### Is this affiliated with Greenhouse, Lever or Ashby?

No.

This is an independent community Actor using their public job-board endpoints.

***

### Support

For bugs, questions, or skill-dictionary requests:

```text
johnatan291303@gmail.com
```

You can also use the **Issues** tab on the Actor page.

***

### Part of the JM Forge suite

Also from the same developer:

- **ATS Hiring Signals Monitor** — persistent hiring-signal monitoring across supported ATS platforms.
- **GitHub Release Monitor & Dependency Alerts** — monitor genuinely new GitHub releases.
- **Shopify Competitor Sale & New Arrival Insights** — Shopify promotion and assortment intelligence.

JM Forge Actors remain independent tools.

Use the tool that matches the problem you need to solve.

# Actor input Schema

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

One entry per company: { slug, ats } with ats one of greenhouse/lever/ashby, or "auto" to detect. A bare string slug also works.

## `includeSalary` (type: `boolean`):

Deterministic range/currency/period parser. When a company does not publish pay, the field says detected: false — nothing is estimated.

## `includeSkills` (type: `boolean`):

Matches a versioned dictionary (word boundaries, no substring hits). The dictionary version travels in every record.

## `skillsFilter` (type: `array`):

Skill keys (e.g. python, kubernetes, salesforce). Empty = no filter. Filters narrow the output, never the detection.

## `seniorityFilter` (type: `array`):

intern, junior, mid, senior, staff, principal, lead, manager, director, vp, c-level. Empty = no filter.

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

Cap per board before enrichment.

## `maxResults` (type: `integer`):

Hard cap on charged records. The run stops gracefully and keeps what it delivered.

## `maxRuntimeMs` (type: `integer`):

Hard time cap. The run stops gracefully when reached.

## `debug` (type: `boolean`):

Log pacing waits and per-company decisions.

## Actor input object example

```json
{
  "companies": [
    {
      "slug": "gitlab",
      "ats": "greenhouse"
    },
    {
      "slug": "linear",
      "ats": "ashby"
    }
  ],
  "includeSalary": true,
  "includeSkills": true,
  "skillsFilter": [],
  "seniorityFilter": [],
  "maxJobsPerCompany": 200,
  "maxResults": 1000,
  "maxRuntimeMs": 300000,
  "debug": false
}
```

# Actor output Schema

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

Default dataset containing ENRICHED\_JOB records and the free RUN\_SUMMARY.

## `stats` (type: `string`):

STATS record with billing, requests, retries, quality signals, limits, runtime, and operational metrics.

# 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": [
        {
            "slug": "gitlab",
            "ats": "greenhouse"
        },
        {
            "slug": "linear",
            "ats": "ashby"
        }
    ],
    "includeSalary": true,
    "includeSkills": true,
    "skillsFilter": [],
    "seniorityFilter": [],
    "maxJobsPerCompany": 200,
    "maxResults": 1000,
    "maxRuntimeMs": 300000,
    "debug": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("johnatan029/ats-salary-skills-intelligence").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": [
        {
            "slug": "gitlab",
            "ats": "greenhouse",
        },
        {
            "slug": "linear",
            "ats": "ashby",
        },
    ],
    "includeSalary": True,
    "includeSkills": True,
    "skillsFilter": [],
    "seniorityFilter": [],
    "maxJobsPerCompany": 200,
    "maxResults": 1000,
    "maxRuntimeMs": 300000,
    "debug": False,
}

# Run the Actor and wait for it to finish
run = client.actor("johnatan029/ats-salary-skills-intelligence").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": [
    {
      "slug": "gitlab",
      "ats": "greenhouse"
    },
    {
      "slug": "linear",
      "ats": "ashby"
    }
  ],
  "includeSalary": true,
  "includeSkills": true,
  "skillsFilter": [],
  "seniorityFilter": [],
  "maxJobsPerCompany": 200,
  "maxResults": 1000,
  "maxRuntimeMs": 300000,
  "debug": false
}' |
apify call johnatan029/ats-salary-skills-intelligence --silent --output-dataset

```

## MCP server setup

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

```

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/nzDlHcJ1lN4C9u2zS/builds/Pg0eCC2C3KvaqsfHd/openapi.json
