# Career Site Jobs API | Greenhouse, Lever, Ashby (`atesen-software/career-site-jobs-api`) Actor

Paste company websites or board URLs and get open jobs from Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee and Personio in one format. Salary, remote flag and new-jobs-only monitoring included. $5 per 1,000 jobs.

- **URL**: https://apify.com/atesen-software/career-site-jobs-api.md
- **Developed by:** [Atesen Software](https://apify.com/atesen-software) (community)
- **Categories:** Jobs, Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

Paste a list of **companies**: websites like `stripe.com`, careers pages, job board URLs or just names. For each one you get **every open job, straight from the company's applicant tracking system (ATS)**, in **one normalised format**. No browser, no proxy, no login, so it is fast and cheap.

- 🔎 **Auto-detects the ATS** from the company website or careers page (links, embedded job boards), or from a board URL, an `ats:board` pair or a company name.
- 🧹 **Normalised fields for every ATS**: title, department/team, location, city, region, **ISO country code**, **workplace type** (remote/hybrid/onsite), **employment type** (full_time, part_time, contract, temporary, internship), seniority, **salary min/max/currency/period** (from the ATS or parsed from the description), posted date, job URL, apply URL, plain-text and/or HTML description.
- ♻️ **Dedupe**: the same board given twice is fetched once, and the same job posted several times (same title and location) is merged.
- 🔔 **Monitoring mode**: returns only **jobs that are new since the last run**, plus free rows for **jobs that were closed**. Schedule it daily and pay only for new jobs.
- ✅ **Never fails on a bad input**: each problem gets a free status row (`unknown_ats`, `unsupported_ats`, `board_not_found`, `unreachable`, `invalid_input`, `duplicate`, `no_jobs`, `no_matching_jobs`, `no_new_jobs`).

### Why this actor

| | **Career Site Jobs API** (this actor) | Career-site job database APIs | Single-ATS scrapers |
|---|---|---|---|
| Price | **$5 per 1,000 jobs** (down to $4 on higher plans), no extra start fee | about **$12 per 1,000 jobs plus $10 per 1,000 runs** | about $2 per 1,000 jobs, one ATS each |
| You choose the companies | ✅ Any company on a supported ATS, **detected from its website** | Search over their pre-built database | Board URL only |
| Freshness | **Live**, read directly from the ATS at run time | Depends on the provider's crawl schedule | Live |
| Normalised fields across ATS | ✅ Country code, workplace type, employment type, salary | ✅ | Each ATS's own format |
| New jobs only / closed jobs | ✅ Built-in state, closed-job rows are free | Date filters | ❌ |
| Dedupe | ✅ Boards and duplicate postings | — | ❌ |
| Problem rows instead of failed runs | ✅ Free | — | — |

Supported ATS: **Greenhouse, Lever (US and EU), Ashby, Workable, SmartRecruiters, Recruitee, Personio**. Workday, Taleo, iCIMS, SuccessFactors, Teamtailor and others are recognised and reported as `unsupported_ats` (not charged).

### How to use it

1. Add companies, one per line: `stripe.com`, `https://jobs.lever.co/palantir`, `ashby:notion`, `Hugging Face`…
2. Optionally filter by title keywords, location, country, workplace type, employment type, department or posting date.
3. Click **Start**, then download the jobs as Excel, CSV or JSON, or use the API.

#### Example input

```json
{
  "companies": ["stripe.com", "https://jobs.lever.co/palantir", "ashby:notion", "huggingface.co", "https://jobs.smartrecruiters.com/BoschGroup"],
  "keywords": ["engineer", "data"],
  "countries": ["US", "GB", "DE"],
  "workplaceTypes": ["remote", "hybrid"],
  "postedWithinDays": 30,
  "descriptionFormat": "text"
}
```

#### Monitor companies for new jobs (daily schedule)

```json
{
  "companies": ["stripe.com", "notion.so", "airbnb.com"],
  "incrementalMode": true,
  "stateKey": "my-target-companies",
  "reportClosedJobs": true
}
```

The first run returns all jobs and remembers them. Later runs return only **new** jobs (`isNew: true`) and a free `closed_job` row for each job that disappeared. Use a different `stateKey` for each watch list.

#### Example output (one job)

```json
{
  "type": "job",
  "title": "Software Engineer, Developer Platform",
  "company": "Notion",
  "department": "Engineering",
  "team": "Engineering",
  "location": "San Francisco, California / New York, New York",
  "locations": ["San Francisco, California", "New York, New York"],
  "city": "San Francisco",
  "region": "California",
  "country": "US",
  "countries": ["US"],
  "workplaceType": "hybrid",
  "employmentType": "full_time",
  "employmentTypeRaw": "FullTime",
  "seniority": null,
  "salaryMin": 179000,
  "salaryMax": 200000,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salaryText": "$179,000 – $200,000 per year",
  "salarySource": "description",
  "postedAt": "2026-10-19T17:28:06.956Z",
  "updatedAt": null,
  "url": "https://jobs.ashbyhq.com/notion/8c0bf773-e536-45c9-9a6c-8ee478148cc8",
  "applyUrl": "https://jobs.ashbyhq.com/notion/8c0bf773-e536-45c9-9a6c-8ee478148cc8/application",
  "jobId": "8c0bf773-e536-45c9-9a6c-8ee478148cc8",
  "requisitionId": null,
  "ats": "ashby",
  "board": "notion",
  "descriptionText": "Who We Are\n\nNotion is the collaborative AI workspace…",
  "isNew": null,
  "firstSeenAt": "2026-10-20T01:25:53.705Z",
  "input": "ashby:notion",
  "scrapedAt": "2026-10-20T01:25:53.705Z"
}
```

Each company also gets one free **board** row: `status`, `company`, `ats`, `board`, `detectedBy` (`website`, `board_url`, `explicit`, `name_match`), `jobsOnBoard`, `jobsMatchingFilters`, `jobsReturned` and, in monitoring mode, `closedJobs`. Open the **Job boards & problems** view to see them.

#### Field reference

| Field | Description |
|---|---|
| `title`, `company`, `department`, `team` | As published by the company |
| `location`, `locations`, `city`, `region` | All locations of the job |
| `country`, `countries` | ISO 3166 alpha-2 codes, from the ATS or parsed from the location |
| `workplaceType` | `remote`, `hybrid`, `onsite` or null when unknown |
| `employmentType`, `employmentTypeRaw` | Normalised (`full_time`, `part_time`, `contract`, `temporary`, `internship`, `other`) and the original value |
| `seniority` | When the ATS has it (Workable, SmartRecruiters, Recruitee, Personio) |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryText`, `salarySource` | From the ATS (`ats`) or found in the description (`description`) |
| `postedAt`, `updatedAt` | ISO timestamps |
| `url`, `applyUrl`, `jobId`, `requisitionId` | Links and IDs |
| `descriptionText` / `descriptionHtml` | Depends on `descriptionFormat` |
| `ats`, `board`, `input` | Where the job came from |
| `isNew`, `firstSeenAt` | Monitoring mode |

### Pricing

Pay per event:

- **Job**: $0.005 ($5 per 1,000 jobs) on the Free and Bronze plans, $0.0045 on Silver, $0.004 on Gold and higher.
- **Free**: board rows, closed-job rows, every problem row, and jobs removed by your filters (filters run before anything is charged).
- A tiny actor start fee set by Apify ($0.00005).

Monitoring mode charges only for **new** jobs, so a daily check of 50 companies usually costs a few cents.

### Tips

- If a company website is not detected (for example the careers page is built only in JavaScript), paste the **job board URL** or an `ats:board` pair. You find the board name in the URL of any job ad: `jobs.lever.co/<board>/…`, `boards.greenhouse.io/<board>/jobs/…`, `jobs.ashbyhq.com/<board>/…`, `apply.workable.com/<board>/…`, `jobs.smartrecruiters.com/<board>/…`, `<board>.recruitee.com`, `<board>.jobs.personio.de`.
- **Company-name matching** is only accepted when the board has jobs and, where the ATS publishes a company name, that name matches. Check `company` and `detectedBy` in the board rows.
- SmartRecruiters needs one request per job for the description. Use filters or `descriptionFormat: "none"` for very large companies (Bosch has 5,000+ jobs).
- 256 MB is enough; use 512 MB for very large boards.

### Is it legal?

This actor reads **public job postings** from the official public job-board APIs that the ATS vendors provide so that companies' jobs can be shown on websites and job sites. It collects no personal data. Respect the companies' terms when republishing their job ads.

# Actor input Schema

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

One per line. Any of: a company website ("stripe.com"), a careers page URL, a job board URL ("https://jobs.lever.co/palantir", "https://boards.greenhouse.io/airbnb"), an explicit "ats:board" pair ("ashby:notion", "workable:huggingface", "smartrecruiters:BoschGroup", "recruitee:bunq", "personio:acme") or a company name. The ATS is detected automatically.

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

Same as Companies, in request-list format, if you already have URL lists from other actors.

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

Only jobs whose title contains at least one of these words (case-insensitive), e.g. "engineer", "data", "sales". Empty = all jobs.

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

Skip jobs whose title contains any of these words, e.g. "intern", "senior".

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

Only jobs whose location contains one of these texts, e.g. "London", "Berlin", "remote".

## `countries` (type: `array`):

Only jobs in these countries: ISO codes or names ("US", "DE", "United Kingdom").

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

Only these workplace types. Jobs whose workplace type is not published (and not stated in the title or location) are excluded when this filter is set.

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

Only these (normalised) employment types. Jobs without a published employment type are excluded when this filter is set.

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

Only jobs whose department or team contains one of these texts, e.g. "Engineering".

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

Only jobs posted in the last N days (0 = any date).

## `incrementalMode` (type: `boolean`):

Remembers the jobs of each board between runs (under the state name below) and returns only jobs that are new. The first run returns all jobs. You pay only for new jobs.

## `stateKey` (type: `string`):

Use a different name for each separate watch list (e.g. "fintech-companies"). Stored in your key-value store "career-jobs-state".

## `reportClosedJobs` (type: `boolean`):

In monitoring mode, also add a free row for each job that disappeared from a board since the last run.

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

Plain text (default), HTML, both, or none (faster, smaller output; SmartRecruiters then needs no extra requests).

## `dedupeSimilarJobs` (type: `boolean`):

Some companies post the same job several times (same title and location). Keep only one.

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

Newest first (0 = no limit).

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

Stop after this many jobs (0 = no limit).

## Actor input object example

```json
{
  "companies": [
    "stripe.com",
    "https://jobs.lever.co/palantir",
    "ashby:notion",
    "huggingface.co"
  ],
  "postedWithinDays": 0,
  "incrementalMode": false,
  "stateKey": "default",
  "reportClosedJobs": true,
  "descriptionFormat": "text",
  "dedupeSimilarJobs": true,
  "maxJobsPerCompany": 0,
  "maxJobs": 0
}
```

# Actor output Schema

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

No description

## `boards` (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": [
        "stripe.com",
        "https://jobs.lever.co/palantir",
        "ashby:notion",
        "huggingface.co"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("atesen-software/career-site-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": [
        "stripe.com",
        "https://jobs.lever.co/palantir",
        "ashby:notion",
        "huggingface.co",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("atesen-software/career-site-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": [
    "stripe.com",
    "https://jobs.lever.co/palantir",
    "ashby:notion",
    "huggingface.co"
  ]
}' |
apify call atesen-software/career-site-jobs-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,atesen-software/career-site-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/uj6vPOha4C7XBEMhU/builds/KgnGZCpD18dCKB22y/openapi.json
