# Job Search API - Career Site Jobs: Greenhouse, Lever +6 ATS (`datasignalslab/job-search-api`) Actor

Job scraper and search API for live openings on company career pages. Searches an index of companies across Greenhouse, Lever, Ashby, Workable, SmartRecruiters and Teamtailor, and returns one normalised schema with apply links. No LinkedIn logins, no proxies, nothing to get blocked by.

- **URL**: https://apify.com/datasignalslab/job-search-api.md
- **Developed by:** [DataSignals Lab](https://apify.com/datasignalslab) (community)
- **Categories:** Jobs, Lead generation, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 job returneds

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

## Job Search API — jobs direct from company career pages

Search live openings across an indexed set of companies, pulled straight from
their own career boards. **Run it with no input to see it work.**

Most job scrapers read LinkedIn or Indeed: aggregators that copy postings a day
or two late, behind bot protection that breaks and needs proxies. This Actor
reads the source instead — the public JSON endpoint every applicant tracking
system already publishes for its customers' career pages.

**You do not need to know any company or platform.** A built-in index maps
companies to their ATS, so you can just search: keyword, location, remote.
Name a company explicitly only when you want to track that one employer.

### What you get

One row per open job, identical across all six platforms:

| field | meaning |
|---|---|
| `title` | job title |
| `company` | the company slug you asked for |
| `platform` | which ATS it was found on (auto-detected) |
| `location` | location as the employer wrote it |
| `remote` | `true`/`false` from the employer's own flag, `null` if unstated |
| `department` | team or department |
| `employment_type` | full-time, contract, and so on where published |
| `posted_at` | ISO-8601 UTC, or `null` if the platform does not publish it |
| `apply_url` | the public application page |
| `job_id` | the platform's own identifier, for de-duplication |
| `description` | the posting text, HTML stripped |

Every field is present on every row. Where a platform genuinely does not publish
something you get `null` — never an invented value.

### Why one schema matters

Six platforms, six shapes. Lever puts the date in epoch milliseconds, Greenhouse
in ISO with a `Z`. SmartRecruiters splits the location into city, region and
country and exposes a `ref` that points at its own API rather than at a page a
human can apply on. Ashby has a real `isRemote` boolean; Greenhouse expects you
to read the location string. Normalising that is the work this Actor does for
you.

### Input

Everything is optional. The most common run is a plain search:

```json
{
  "keywords": ["engineer"],
  "location": "Berlin",
  "remoteOnly": true,
  "maxJobs": 100
}
```

When you do name companies, the slug is the identifier in the career-board URL:
`boards.greenhouse.io/stripe` → `stripe`, `jobs.ashbyhq.com/Ramp` → `Ramp`.
The platform is detected automatically either way.

| input | default | what it does |
|---|---|---|
| `keywords` | `["engineer"]` | match on job title and department |
| `location` | `""` | substring match on location |
| `remoteOnly` | `false` | only jobs the employer flagged remote |
| `maxJobs` | `100` | hard stop, and therefore your budget cap |
| `maxCompanies` | `40` | how many indexed companies to walk |
| `maxJobsPerCompany` | `10` | keeps one big employer from filling the page |
| `companies` | `[]` | search these slugs instead of the index |
| `companyFilter` | `""` | limit the index to matching company names |
| `platform` | `""` | limit to one ATS platform |
| `searchDescriptions` | `false` | also match the description text |

Keywords deliberately do **not** search descriptions by default. Nearly every
job description contains the word "engineer" somewhere, so including them turns
a search for engineers into a list that also has analysts and recruiters in it.

A company that is not on any supported platform is logged as a warning and
skipped; the rest of your list still runs.

### Pricing

**Pay per job returned.** You pay for rows you actually get: filtered-out jobs
and companies that returned nothing cost nothing. No subscription, no
per-run minimum.

### Use it from an AI agent

The Actor works with no input at all, which means an agent can call it to see
what it does before deciding how to use it. It is also available over MCP, so
Claude, ChatGPT or Cursor can query live job boards directly.

### Limits, stated plainly

- Only companies that publish a public career board on one of the six supported
  platforms. That is a large share of tech and scale-up hiring, not all hiring.
- The index is a curated set, not the whole internet. It grows over time; name
  companies explicitly to reach any employer that is not in it yet.
- `posted_at` is whatever the platform publishes. Teamtailor and some Workable
  boards do not expose a reliable date; you get `null` rather than a guess.
- `remote` reflects the employer's own flag or wording. Employers are
  inconsistent about this; treat it as their claim, not as verified fact.
- No salary field. Too few boards publish it in a comparable way for it to be
  worth a column that is empty nine times out of ten.

# Actor input Schema

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

Matches job title, department and description. Case-insensitive. Leave empty to get every open job from the companies searched.

## `location` (type: `string`):

Substring match on the location exactly as the employer wrote it, e.g. 'Berlin' or 'Amsterdam'. Use this for cities; use Country for countries.

## `country` (type: `string`):

Normalised country filter, e.g. 'Germany', 'United States', 'India'. More reliable than the location field: employers write 'Remote Canada' or 'Mountain View, CA' and this resolves both. Empty means any country.

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

Keep only jobs the employer flagged as remote.

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

Hard stop across all companies. You pay per job returned, so this is also your budget cap. 0 means no limit.

## `maxCompanies` (type: `integer`):

How many indexed companies to walk through. 0 means all of them.

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

Job-board slugs to search instead of the index, e.g. 'stripe', 'Ramp'. Leave empty to search the built-in company index. The ATS platform is detected automatically.

## `companyFilter` (type: `string`):

Only search indexed companies whose name contains this text. Ignored when you list companies explicitly.

## `platform` (type: `string`):

Optional. Only search companies on this platform.

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

Stops one large employer from filling your whole result set. Defaults to 10 so results span many companies; set 0 for no cap.

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

Off by default: nearly every description mentions words like 'engineer', which pulls in unrelated roles. Turn on for a deliberately broad search.

## Actor input object example

```json
{
  "keywords": [
    "engineer"
  ],
  "location": "",
  "country": "",
  "remoteOnly": false,
  "maxJobs": 100,
  "maxCompanies": 40,
  "companies": [],
  "companyFilter": "",
  "platform": "",
  "maxJobsPerCompany": 10,
  "searchDescriptions": 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 = {
    "keywords": [
        "engineer"
    ],
    "maxJobs": 100,
    "maxCompanies": 40,
    "maxJobsPerCompany": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("datasignalslab/job-search-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 = {
    "keywords": ["engineer"],
    "maxJobs": 100,
    "maxCompanies": 40,
    "maxJobsPerCompany": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("datasignalslab/job-search-api").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": [
    "engineer"
  ],
  "maxJobs": 100,
  "maxCompanies": 40,
  "maxJobsPerCompany": 10
}' |
apify call datasignalslab/job-search-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=datasignalslab/job-search-api",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/zUdQ0ByWojQmUbS8m/builds/cEr0qdBmhuW6KAhIV/openapi.json
