# Career Site Jobs Search - 9 ATS, Only New Jobs, No Duplicates (`that_red_bird/career-site-jobs-search`) Actor

Search jobs by keyword across the career pages of thousands of companies at once: Greenhouse, Lever, Ashby, Workday and 5 more hiring platforms. Only jobs new since your last run, no duplicates, salary and remote flags parsed. $2 per 1,000 jobs.

- **URL**: https://apify.com/that_red_bird/career-site-jobs-search.md
- **Developed by:** [mohamed alaya](https://apify.com/that_red_bird) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 job founds

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

## Career Site Jobs Search - 9 ATS, Only New Jobs, No Duplicates

Search jobs by keyword across the career pages of thousands of companies at once.
Type what you are looking for (data engineer, remote) -> get clean job rows straight from each company's own hiring page, in about 1 to 3 minutes, and on a schedule only the jobs that are new since your last run.

It reads the public job-board feeds of nine hiring platforms: Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Personio, Breezy HR and Workday. No login, no cookies, no LinkedIn or Indeed scraping.

### What you get

| Field | Meaning |
|---|---|
| title, company, companySlug | Job title and the company that posted it |
| location, city, region, country, countryCode | Where the job is |
| workplaceType, isRemote | remote, hybrid or onsite (read from the board, or from the title and location) |
| seniority | intern, junior, mid, senior, staff, lead, manager, director or executive, read from the title |
| employmentType, department, team | full_time, part_time, contract ... and the team |
| salaryMin, salaryMax, salaryCurrency, salaryPeriod | Parsed salary range when the company publishes one |
| postedAt, updatedAt | When the job was posted, when the board gives a date |
| url, applyUrl, boardUrl | The job page, the apply link, and the company's board |
| source | Which platform it came from (greenhouse, lever, ashby ...) |
| matchedKeyword | Which of your keywords matched |
| isNew | true when running in "only new" mode |
| description | Full cleaned text, only when you switch "Include job descriptions" on |

### 3 ways to use it

1. **Find jobs for a keyword**
   `{"keywords": ["data engineer"], "maxResults": 200}`
2. **Remote senior roles posted this week**
   `{"keywords": ["backend", "platform engineer"], "locations": ["remote"], "seniority": ["senior+"], "postedWithinDays": 7, "maxResults": 500}`
3. **A daily fresh-jobs feed (pay only for new jobs)**
   `{"keywords": ["product designer"], "onlyNew": true, "feedName": "designer-eu", "locations": ["berlin", "london"], "maxBoards": 3000, "maxRuntimeMinutes": 20, "maxResults": 0}` and schedule it daily.

### Input

| Field | What to enter | Default |
|---|---|---|
| keywords | Job words. Any of them may match; a keyword with several words needs all its words in the title, department or team | software engineer |
| excludeKeywords | Drop jobs containing these (intern, sales ...) | none |
| matchMode | All words in any order, or exact phrase | all words |
| searchDescriptions | Also search the job text (slower, more matches) | off |
| locations, countries, remoteOnly | Where. Type `remote` in locations to include remote jobs | anywhere |
| seniority, employmentTypes | Level and contract type filters | all |
| postedWithinDays | Only jobs posted in the last N days (jobs without a date are kept) | 30 |
| minSalary | Yearly minimum, for jobs that publish pay | 0 (off) |
| maxResults | Stop after N jobs, newest first. This is also your spending cap. 0 = no cap | 25 |
| maxPerCompany | Cap jobs from any one company, so one employer posting a role in 40 cities does not fill the list. 0 = no cap | 0 |
| onlyNew, feedName | Return only jobs not delivered before; one feed name per saved search | off, default |
| companies | Search only these companies (name, domain, careers link or `greenhouse:stripe`). Clear `keywords` to list every job of a company | all known boards |
| platforms | Limit to some hiring platforms | all nine |
| maxBoards, maxRuntimeMinutes | How wide and how long to search | 300 boards, 3 minutes |
| includeDescription, dedupe, webhookUrl | Descriptions, duplicate removal, Slack/Discord/Zapier/Make/n8n webhook | off, on, none |
| refreshBoardList, refreshBoardListDays | Re-discover company boards from the public Common Crawl index | off, every 30 days |

The prefilled input works as-is: it returns 25 software engineer jobs.

### Output example (a real row from a run)

```json
{
  "title": "Delivery Solutions Architect - Public Sector (SLED)",
  "company": "Databricks",
  "source": "greenhouse",
  "location": "USCA",
  "workplaceType": null,
  "isRemote": false,
  "seniority": "lead",
  "salaryMin": 180000,
  "salaryMax": 247500,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "postedAt": "2026-10-01T14:20:08.000Z",
  "url": "https://databricks.com/company/careers/open-positions/job?gh_jid=8790285002",
  "boardUrl": "https://boards.greenhouse.io/databricks",
  "matchedKeyword": "solutions architect",
  "isNew": true
}
```

Download as JSON, CSV or Excel from the Storage tab.

### Pricing (pay per event)

| Event | Price |
|---|---|
| Job found (one job row delivered) | $0.002 |

You pay only for jobs that land in your dataset. If nothing matches, you pay nothing (you get one free notice row that explains why).

- 100 jobs = **$0.20**
- 1,000 jobs = **$2.00**
- A daily "only new" feed of 5,000 new jobs a week = **$10 per week**

Other career-site job APIs on the Store charge about $0.012 per job; this one is $0.002 and adds only-new mode.

### Use it with

- **Before / after:** feed `company` names into `that_red_bird/company-enrichment-by-domain` to get each employer's profile; run `that_red_bird/hiring-signal-monitor` to watch a company's hiring over time; send rows to `that_red_bird/lead-list-deduplicator` to merge lists.
- **Make, Zapier, n8n:** schedule the Actor, trigger on "run succeeded", send rows to Google Sheets, Slack, Airtable or your ATS. Or set `webhookUrl` and get the new jobs pushed to you.
- **AI agents / MCP:** call `that_red_bird/career-site-jobs-search` with `{"keywords": ["ml engineer"], "locations": ["remote"], "maxResults": 20}` and the agent gets structured job rows with salary and seniority.

### FAQ

**How many companies does it search?** It starts from a verified list of 5,700+ company career boards shipped with the Actor, and grows its own saved list from the public Common Crawl web index every 30 days. The default run reads the 300 biggest boards for speed; raise `maxBoards` (and `maxRuntimeMinutes`) to sweep thousands.

**Why do I get fewer results than I expected?** The default run is capped at 25 jobs and 300 boards. Raise `maxResults`, `maxBoards` and `maxRuntimeMinutes`. The run summary (SUMMARY in the Key-value store) says how many boards were read and how many were skipped because of the time limit.

**How does "only new" work?** Each feed name remembers the jobs it already delivered (in a named key-value store in your account). The first run returns everything that matches; later runs return only jobs not seen before. Use one feed name per saved search.

**Are duplicates removed?** Yes: the same company, title and location appearing twice is collapsed. This also catches a company posting on two platforms.

**Is it legal?** It reads the public job-board feeds that companies publish for job seekers and aggregators. Check the terms of any job data you republish.

**Why is the salary empty?** Only some companies publish pay. We parse it from the board's pay fields, and, with descriptions on, from the text. Nothing is guessed.

**Do you cover LinkedIn or Indeed?** No. Those sites block scrapers and are not covered. This Actor reads companies' own career sites.

**Can a company be missing?** Yes. Only companies on the nine supported platforms with a known board are searched. Add a company yourself with the `companies` field (name, domain or careers link).

### Limits

- No LinkedIn, Indeed, Glassdoor or aggregator data.
- Nine platforms only; companies with a self-built careers page are not covered.
- Posting dates depend on the platform; Workday only reveals the day (or "30+ days ago").
- The board list is discovered from public web crawls, so very new companies can take a few weeks to appear. Add them with `companies` right away.

### Support

Open an issue on the Issues tab; we answer quickly. If this saved you time, a short review helps other people find it.

# Actor input Schema

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

Jobs matching ANY of these are returned. A keyword with several words (data engineer, python backend) matches when all of its words appear in the job title, department or team, in any order. Leave empty to list every job on the boards searched.

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

Drop jobs whose title, department or team contains any of these words, for example intern, sales, recruiter.

## `matchMode` (type: `string`):

'All words, any order' is forgiving: data engineer also finds Senior Data Platform Engineer. 'Exact phrase' needs the words next to each other, exactly as typed.

## `searchDescriptions` (type: `boolean`):

Also look for the keywords inside the full job text, not only the title. Finds more jobs (and more noise) and makes the run slower because descriptions have to be downloaded.

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

Keep jobs whose location contains any of these, for example berlin, london, california. Type remote to include remote jobs. Leave empty for anywhere.

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

Keep only jobs in these countries, by name or two-letter code, for example United States, DE, United Kingdom. Leave empty for all countries.

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

Keep only fully remote jobs. Hybrid jobs are excluded.

## `seniority` (type: `array`):

Keep only these levels, read from the job title. Leave empty for all. 'Senior and above' keeps senior, staff, lead, manager, director and executive jobs.

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

Keep only these employment types. Leave empty for all.

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

Keep only jobs posted in the last N days. 0 means no limit. Jobs whose board shows no posting date are kept.

## `minSalary` (type: `integer`):

Keep only jobs that publish a salary whose upper end is at least this many units of the posted currency per year (hourly and monthly pay is converted to yearly). 0 means no filter. Needs 'Include job descriptions' for boards that hide pay in the text.

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

Stop after this many jobs, newest first. You are charged per job returned, so this is also your spending cap. 0 means no cap.

## `maxPerCompany` (type: `integer`):

Return at most this many jobs from any one company, so a company posting the same role in many cities cannot crowd out everyone else. 0 means no limit.

## `onlyNew` (type: `boolean`):

Return only jobs that an earlier run with the same 'Feed name' has not already returned. The first run returns everything that matches. Schedule the Actor daily or weekly for a fresh-jobs feed; you pay only for the new jobs.

## `feedName` (type: `string`):

Names the memory used by 'Only jobs new since my last run'. Use one name per saved search (for example data-engineer-berlin) so searches do not share what they have already seen.

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

Search only these companies instead of all known boards. Use a company name (Notion), a domain (ramp.com), a careers link (https://jobs.lever.co/spotify, https://boards.greenhouse.io/gitlab, https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite) or a shorthand like greenhouse:stripe. Leave empty to search everything.

## `platforms` (type: `array`):

Limit the search to these platforms. Leave empty to search all nine.

## `maxBoards` (type: `integer`):

How many company career boards to search, largest first. The default of 300 keeps a first run fast. Raise it (for example 5000) for a wide sweep and also raise 'Max run time'.

## `maxRuntimeMinutes` (type: `integer`):

The search stops starting new boards after this many minutes and returns what it has. Raise it together with 'Max company boards to read'.

## `dedupe` (type: `boolean`):

Collapse jobs with the same company, title and location that appear more than once, for example on two boards of one company.

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

Return the full cleaned description text. Makes runs slower and rows larger, but also finds salaries that are only mentioned in the text.

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

A summary plus up to 100 jobs is POSTed here when jobs are returned. Works with Slack, Discord, Zapier, Make and n8n.

## `refreshBoardList` (type: `boolean`):

Re-discover company boards from the public Common Crawl web index before searching. The list is refreshed automatically every 30 days anyway; this forces it, which takes up to 45 seconds.

## `refreshBoardListDays` (type: `integer`):

How old the saved company board list may get before it is refreshed from the public web index.

## `concurrency` (type: `integer`):

How many company boards are read in parallel. The default is polite and fast enough.

## Actor input object example

```json
{
  "keywords": [
    "software engineer"
  ],
  "excludeKeywords": [],
  "matchMode": "all_words",
  "searchDescriptions": false,
  "locations": [],
  "countries": [],
  "remoteOnly": false,
  "seniority": [],
  "employmentTypes": [],
  "postedWithinDays": 30,
  "minSalary": 0,
  "maxResults": 25,
  "maxPerCompany": 0,
  "onlyNew": false,
  "feedName": "default",
  "companies": [],
  "platforms": [],
  "maxBoards": 300,
  "maxRuntimeMinutes": 3,
  "dedupe": true,
  "includeDescription": false,
  "webhookUrl": "",
  "refreshBoardList": false,
  "refreshBoardListDays": 30,
  "concurrency": 8
}
```

# Actor output Schema

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

No description

## `downloadCsv` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `count` (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 = {
    "keywords": [
        "software engineer"
    ],
    "excludeKeywords": [],
    "matchMode": "all_words",
    "searchDescriptions": false,
    "locations": [],
    "countries": [],
    "remoteOnly": false,
    "seniority": [],
    "employmentTypes": [],
    "postedWithinDays": 30,
    "minSalary": 0,
    "maxResults": 25,
    "maxPerCompany": 0,
    "onlyNew": false,
    "feedName": "default",
    "companies": [],
    "platforms": [],
    "maxBoards": 300,
    "maxRuntimeMinutes": 3,
    "dedupe": true,
    "includeDescription": false,
    "webhookUrl": "",
    "refreshBoardList": false,
    "refreshBoardListDays": 30,
    "concurrency": 8
};

// Run the Actor and wait for it to finish
const run = await client.actor("that_red_bird/career-site-jobs-search").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 = {
    "keywords": ["software engineer"],
    "excludeKeywords": [],
    "matchMode": "all_words",
    "searchDescriptions": False,
    "locations": [],
    "countries": [],
    "remoteOnly": False,
    "seniority": [],
    "employmentTypes": [],
    "postedWithinDays": 30,
    "minSalary": 0,
    "maxResults": 25,
    "maxPerCompany": 0,
    "onlyNew": False,
    "feedName": "default",
    "companies": [],
    "platforms": [],
    "maxBoards": 300,
    "maxRuntimeMinutes": 3,
    "dedupe": True,
    "includeDescription": False,
    "webhookUrl": "",
    "refreshBoardList": False,
    "refreshBoardListDays": 30,
    "concurrency": 8,
}

# Run the Actor and wait for it to finish
run = client.actor("that_red_bird/career-site-jobs-search").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 '{
  "keywords": [
    "software engineer"
  ],
  "excludeKeywords": [],
  "matchMode": "all_words",
  "searchDescriptions": false,
  "locations": [],
  "countries": [],
  "remoteOnly": false,
  "seniority": [],
  "employmentTypes": [],
  "postedWithinDays": 30,
  "minSalary": 0,
  "maxResults": 25,
  "maxPerCompany": 0,
  "onlyNew": false,
  "feedName": "default",
  "companies": [],
  "platforms": [],
  "maxBoards": 300,
  "maxRuntimeMinutes": 3,
  "dedupe": true,
  "includeDescription": false,
  "webhookUrl": "",
  "refreshBoardList": false,
  "refreshBoardListDays": 30,
  "concurrency": 8
}' |
apify call that_red_bird/career-site-jobs-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,that_red_bird/career-site-jobs-search"
        }
    }
}
```

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/j2EbEDbjunU8u3nxS/builds/aSfqHEmdUfK83heST/openapi.json
