# Company Career Site Jobs Scraper (ATS) (`mina_safwat/ats-jobs-scraper`) Actor

Collects every open job from company career sites on Workday, Greenhouse, Lever, Ashby, SmartRecruiters and Workable, with salary, location, remote status and full description in one clean format.

- **URL**: https://apify.com/mina_safwat/ats-jobs-scraper.md
- **Developed by:** [Mina](https://apify.com/mina_safwat) (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

$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

### What does Company Career Site Jobs Scraper do?

**Company Career Site Jobs Scraper** collects **every open job straight from a company's own career site**, on the six applicant tracking systems most companies use: **Workday, Greenhouse, Lever, Ashby, SmartRecruiters and Workable**. Paste a company's job board or careers page link and get each job's **title, locations, country, remote status, employment type, department, posting date, salary range and full description** in one clean, consistent format, whichever system the company runs.

Try it with the two prefilled companies: open the Actor, click **Start**, and the jobs are ready in a few seconds. Because it runs on the Apify platform, you also get API access, scheduled runs, integrations (Google Sheets, Make, Zapier, Slack, webhooks) and run monitoring.

### Why scrape company career sites?

- **Fresher than job boards.** A company's own career site is where a job appears first and where it disappears when filled, so you never chase stale reposts.
- **Salary benchmarking.** Pay ranges are read from the board's own pay fields where it has them, and from the posting text otherwise, then normalized to minimum, maximum, currency and period.
- **Competitor and hiring intelligence.** Track which teams a company is growing, where it is hiring, and how fast, by running the same list on a schedule.
- **Job aggregation.** Feed a niche job board, a newsletter or an internal tool with clean rows from hundreds of companies.
- **Recruiting and sales leads.** A company hiring for a role is often a company that needs your product or your candidates.

### How to scrape jobs from a company career site

1. Open the company's careers page and click through to its list of open jobs.
2. Copy the link from your browser's address bar. Links like `boards.greenhouse.io/airbnb`, `jobs.lever.co/zoox`, `jobs.ashbyhq.com/ramp`, `nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`, `careers.smartrecruiters.com/ServiceNow` and `apply.workable.com/huggingface` all work, and so does a link to any single job on those boards.
3. Paste it into **Company job board or careers page links**, one company per line. Mix systems freely.
4. Optionally narrow the results with **keywords**, **locations**, **remote only** or **posted within (days)**.
5. Click **Start**, then download the results when the run finishes.

**Only have the company's own careers page?** Paste it (for example `https://www.figma.com/careers/`). The Actor looks for the job board the page links to or embeds, and otherwise checks whether a board under the company's name exists, keeping it only when its jobs really are that company's.

**Filters in the link are kept.** On Workday, filter the career site by job family, location or search term, then copy the link: the same filters apply. Lever's team, location and commitment filters are kept the same way.

### Input

Everything is set on the Input tab. In JSON, the input looks like this:

```json
{
    "startUrls": [
        "https://job-boards.greenhouse.io/airbnb",
        "https://jobs.ashbyhq.com/ramp",
        "https://nvidia.wd5.myworkdayjobs.com/en-US/NVIDIAExternalCareerSite"
    ],
    "keywords": ["engineer"],
    "locations": ["US", "London"],
    "remoteOnly": false,
    "postedWithinDays": 30,
    "maxJobsPerCompany": 100,
    "includeDescription": true
}
```

| Field | What it does |
|---|---|
| `startUrls` | Company job board or careers page links, one per company. |
| `keywords` | Keeps jobs whose title contains one of these words. Words match where a word starts, so `engineer` also finds "Engineering Manager". Words of three letters or fewer must match whole, so `Go` skips "Government", and terms such as `.NET`, `C++` or `R&D` work as written. |
| `locations` | Keeps jobs in one of these places: a city, region, country name or two-letter country code. A two-letter code is always a country: `FR` keeps jobs in France, never San Francisco, and `US` also keeps US jobs listed as "City, ST". To narrow to a US state, list its cities (boards write "San Francisco, CA"), as a state code would read as a country. |
| `remoteOnly` | Keeps only jobs marked remote, or whose location says remote. |
| `postedWithinDays` | Keeps jobs posted in the last N days. `0` keeps all. |
| `maxJobsPerCompany` | Stops after this many matching jobs per link. Default: 1,000. |
| `includeDescription` | Adds the full job description as plain text. Default: on. |
| `proxyConfiguration` | Usually not needed. If a board refuses, the Actor retries through Apify's residential proxy on its own when your plan includes it. |

### Output

Each job becomes one row. Here is a real example, with its description shortened:

```json
{
    "jobId": "f564dcf9-9390-4a3f-896f-8047a5086040",
    "title": "Mobile Engineer, Android",
    "company": "ramp",
    "ats": "ashby",
    "url": "https://jobs.ashbyhq.com/ramp/f564dcf9-9390-4a3f-896f-8047a5086040",
    "applyUrl": "https://jobs.ashbyhq.com/ramp/f564dcf9-9390-4a3f-896f-8047a5086040/application",
    "locations": ["New York, NY (HQ)", "Remote (Canada)", "San Francisco, CA", "Remote (US)"],
    "country": "US",
    "isRemote": true,
    "workplaceType": "hybrid",
    "employmentType": "full-time",
    "department": "Engineering",
    "postedAt": "2025-07-31T21:58:34Z",
    "salaryMin": 189000,
    "salaryMax": 330000,
    "salaryCurrency": "USD",
    "salaryPeriod": "year",
    "salarySource": "structured",
    "requisitionId": null,
    "descriptionText": "ABOUT RAMP\n\nRamp is building the smart infrastructure for finance teams...",
    "sourceUrl": "https://jobs.ashbyhq.com/ramp",
    "scrapedAt": "2026-10-01T18:17:13Z"
}
```

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel. The Output tab has two views: **Jobs** (the main columns) and **Full details** (IDs, apply link and description).

### Data fields

| Field | Description |
|---|---|
| `jobId` | The job's ID on its board. |
| `title` | Job title. |
| `company` | Company name as the board shows it; for Lever, Ashby and Workday boards, the company's name in the link (such as `ramp`). |
| `ats` | The system the board runs on: `workday`, `greenhouse`, `lever`, `ashby`, `smartrecruiters` or `workable`. |
| `url` | Link to the job posting. |
| `applyUrl` | Link to the application form. Missing for SmartRecruiters jobs when descriptions are off. |
| `locations` | Every location the job lists. |
| `country` | ISO two-letter country code, when the board gives a country or the location ends with one. |
| `isRemote` | `true` when the board marks the job remote or a location says remote. |
| `workplaceType` | `remote`, `hybrid` or `onsite`, when the board says. |
| `employmentType` | `full-time`, `part-time`, `contract`, `temporary` or `internship`, when the board says. |
| `department` | Department or team. Workday boards do not publish one. |
| `postedAt` | When the job was posted, in UTC. |
| `salaryMin`, `salaryMax` | The pay range. When a posting lists several ranges in one currency (by region or level), the range spans all of them. |
| `salaryCurrency` | ISO currency code, such as `USD` or `EUR`. |
| `salaryPeriod` | `year`, `month`, `week`, `day` or `hour`. |
| `salarySource` | `structured` when the board has its own pay fields, `text` when the range was read from the posting. |
| `requisitionId` | The company's internal requisition number, when published. |
| `descriptionText` | The full job description as plain text. |
| `sourceUrl` | The link you pasted that led to this job. |
| `scrapedAt` | When the job was collected, in UTC. |

### How much does it cost to scrape company career sites?

The Actor uses **pay-per-event** pricing, so you pay only for jobs saved:

- **$0.0015 per job**, which is **$1.50 per 1,000 jobs**, with the full description and parsed salary included. There is no start fee.

For example, all 157 open jobs at Airbnb cost about $0.24. Apify's free plan includes $5 of monthly credit, which covers roughly 3,300 jobs. Links that return nothing are never charged as results, and if you set a maximum cost for a run, the Actor stops as soon as it is reached.

### Tips for scraping career sites

- **Filter on the board first.** On Workday, filtering the career site by job family or location before copying the link means fewer jobs are read, so runs are faster.
- **Use keywords and locations** to keep only the jobs you need; you only pay for jobs that match.
- **Set `maxJobsPerCompany`** to cap very large employers.
- **Schedule runs** (for example daily, with `postedWithinDays: 1`) to monitor new openings.
- **Turn off `includeDescription`** if you only need titles, locations and pay. Pay ranges are still read on every board except SmartRecruiters, where the description is what holds them.
- Pasting the same board twice, or two links to the same board, scrapes it once.

### FAQ, disclaimers and support

**Which career sites are supported?** Company boards on Workday (`myworkdayjobs.com` and `myworkdaysite.com`), Greenhouse, Lever, Ashby, SmartRecruiters and Workable, plus companies' own careers pages that lead to one of them. Workable's cross-company job search is not supported; paste a company's own Workable page instead.

**Why do some jobs have no salary?** Many companies don't publish pay. Where they do, the Actor reads it; Greenhouse, Lever and Ashby often have dedicated pay fields, while Workday, SmartRecruiters and Workable pay ranges are read from the posting text.

**What happens to links that return nothing?** They are not saved as rows, so you are not charged for them. Each is listed with its reason (board doesn't exist, no open jobs, no jobs matching your filters, unusable link) in the `FAILED_INPUTS` record of the run's key-value store, and the run's status message gives the counts. A run fails only when every board refused or could not be read.

**Is it legal to scrape company career sites?** This Actor collects publicly posted job listings that companies publish so that people can find and apply for them. It collects no personal data. You are still responsible for making sure your use complies with each site's terms and with the laws that apply to you, such as data protection and copyright rules for republishing descriptions. If in doubt, ask a lawyer.

**Found a problem or need a custom solution?** Open an issue on the **Issues** tab and we will look into it. Custom scrapers and job data feeds are available on request.

# Actor input Schema

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

One link per company, one per line. Paste the company's job board on Workday (<tenant>.wd5.myworkdayjobs.com/<site>), Greenhouse (boards.greenhouse.io/<company>), Lever (jobs.lever.co/<company>), Ashby (jobs.ashbyhq.com/<company>), SmartRecruiters (careers.smartrecruiters.com/<company>) or Workable (apply.workable.com/<company>). A link to a single job works too and gives that company's whole board. You can also paste a company's own careers page (for example https://www.figma.com/careers/): the Actor looks for the job board behind it. Filters in Workday and Lever links (location, team, job family, search term) are kept.

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

Keep only jobs whose title contains one of these words, for example "engineer" or "product manager". A word matches where a word starts, so "engineer" also finds "Engineering Manager". Leave empty for every job.

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

Keep only jobs in one of these places: a city, region, country name or two-letter country code, for example "London", "Germany" or "US". A two-letter code is always a country, so for a US state list its cities instead. Leave empty for every location.

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

Keep only jobs the company marks as remote, or whose location says remote.

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

Keep only jobs posted in the last N days. 0 keeps every job, however old.

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

Stops after this many jobs from each link. Filters are applied first, so this counts matching jobs.

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

Adds each job's full description as plain text. Turning it off makes rows smaller; for SmartRecruiters boards it also skips one request per job, so pay ranges written in the description are not read.

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

Usually not needed: job boards answer the Actor directly. If a board refuses, the Actor retries through Apify's residential proxy on its own when your plan includes it. Choose a proxy here only to route every request through it.

## Actor input object example

```json
{
  "startUrls": [
    "https://job-boards.greenhouse.io/airbnb",
    "https://jobs.ashbyhq.com/ramp"
  ],
  "keywords": [],
  "locations": [],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "maxJobsPerCompany": 20,
  "includeDescription": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (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 = {
    "startUrls": [
        "https://job-boards.greenhouse.io/airbnb",
        "https://jobs.ashbyhq.com/ramp"
    ],
    "maxJobsPerCompany": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("mina_safwat/ats-jobs-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": [
        "https://job-boards.greenhouse.io/airbnb",
        "https://jobs.ashbyhq.com/ramp",
    ],
    "maxJobsPerCompany": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("mina_safwat/ats-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    "https://job-boards.greenhouse.io/airbnb",
    "https://jobs.ashbyhq.com/ramp"
  ],
  "maxJobsPerCompany": 20
}' |
apify call mina_safwat/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

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

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/4CVnGVdo4X1G7OB92/builds/HcsVoTK7EgBnC7d4h/openapi.json
