# 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 sites

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.**

### Why this is different

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. No login, no key, no
anti-bot workaround, and the posting is here before an aggregator copies it.

**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, country,
remote. Name a company explicitly only when you want to track that one employer.

### Who it's for

- Recruiters and sourcers who want the posting before it reaches an aggregator.
- Job boards and search products that need a clean, normalised feed.
- Analysts tracking hiring as a growth signal per company.
- AI agents that need structured openings instead of scraped HTML.

### Which systems it reads

Eight applicant tracking systems: **Greenhouse, Lever, Ashby, Workable,
SmartRecruiters, Teamtailor, Breezy and Rippling.** The platform is detected
automatically; you never pass it unless you want to narrow the search.

### What you get

One row per open job, identical across all eight 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 |
| `country` | normalised country, or `null` when the location does not say |
| `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

Eight platforms, eight 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. Rippling writes locations like
"Remote (Connecticut, US)". 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 |
| `country` | `""` | match on the normalised country |
| `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.

### How the index grows

The company index is built by reading the ATS link straight from a company's
careers page, not by guessing slugs from company names. That distinction is the
whole method: `devin.ai` runs on Ashby under the slug `cognition` and `split.io`
on Greenhouse under `harnessinc`. No derivation of the company name would ever
find those. A nightly job works through a public list of domains, reads the ATS
link where it finds one, and adds the company only after that ATS actually
returns real jobs.

Jobs themselves are never cached. Every run fetches them live from the employer,
so the index only ever maps company to platform and a buyer cannot receive a
stale posting.

### Use with AI agents and automation

The Actor works with no input at all, so an agent can call it to see what it does
before deciding how to use it. It is also reachable over MCP, so Claude, ChatGPT
or Cursor can query live job boards directly. See the
[MCP server](https://datasignalslab.com/datasignals-mcp.html).

### Scope and limits

- Only companies that publish a public career board on one of the eight
  supported platforms. That is a large share of tech and scale-up hiring, not
  all hiring. Workday and iCIMS are not supported yet.
- The index is a curated and growing set, not the whole internet. Name companies
  explicitly to reach an 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.

### 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. `maxJobs` is therefore your hard budget cap.

#### Subscriptions and shared agent access

The only signal source in this niche with a daily verifiable track record, confluence across every data stream, and direct agent access over MCP. [See the track record](https://datasignalslab.com/proof.html)

Every score in every report lists the terms it was built from, so you can check the number instead of trusting it.

This Actor is part of [DataSignals Lab](https://datasignalslab.com). It does not feed the signal reports; the plans below cover those and the shared MCP access. All prices are listed on the [pricing page](https://datasignalslab.com/pricing.html):

- **Free.** Report previews, a weekly signal digest and 50 free MCP calls per month for AI agents.
- **Snapshot, $19 one time.** The current edition of one report plus 30 days of updates.
- **DataSignals Pro, $29 per month or $290 per year.** All three reports refreshed monthly, MCP access for AI agents (2000 calls per month fair use), email alerts. Cancel anytime.

### Data source and compliance

Public JSON endpoints published by the ATS platforms themselves for their
customers' career pages. No login, no scraping of protected pages, no proxies.
Each row links back to the employer's own application page, so any result can be
checked at its origin.

### FAQ

**Do I need to know which system a company uses?** No. The index resolves it,
and you can search on keyword, location and country alone.

**How fresh are the jobs?** They are fetched from the employer at the moment you
run it. There is no cached copy.

**Can I track one specific employer?** Yes. Name the company and you get only
that employer's openings.

**Is this scraping?** No. These are the public JSON endpoints the ATS platforms
publish for their customers' career pages.

**Why is a company I know missing?** Either its ATS is not one of the eight
supported systems, or it is not in the index yet. Name it explicitly and it will
still be searched.

# 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/GKtfFz0joDINHPi1N/openapi.json
