# Startup Jobs Search: Greenhouse, Lever & Ashby career pages (`kindlinglabs/startup-jobs-search`) Actor

Search open roles across thousands of startup career pages in one run. Filter by title keywords, location, remote, department, and posting date. Pre-built index refreshed every few hours; no login, no proxies.

- **URL**: https://apify.com/kindlinglabs/startup-jobs-search.md
- **Developed by:** [David Braun](https://apify.com/kindlinglabs) (community)
- **Categories:** Jobs, Lead generation, Developer tools
- **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.

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

## Startup Jobs Search: Greenhouse, Lever & Ashby career pages in one query

Search open roles across thousands of startup career pages in a single run.
Filter by title keywords, location, remote status, department, company, and
posting date. Results come from a pre-built index of company job boards hosted
on Greenhouse, Lever, and Ashby, refreshed every few hours, so a search over
every company finishes in seconds instead of crawling each site.

No login, no API keys, no proxies, no browser. The data comes from the public
job-board endpoints each company publishes for its own careers page.

### What you can do with it

- **Job search across companies.** "Every *staff engineer* role posted in the
  last 7 days that is remote-friendly." One run, one CSV.
- **Hiring signals for sales.** Track which companies just opened roles in
  your category. A hiring spike is a buying signal.
- **Recruiting and sourcing.** Watch competitors' openings by department, or
  find companies hiring for a skill you place.
- **Market research.** Salary ranges where companies publish them, remote
  policy by company, hiring volume by sector.
- **Alerts and pipelines.** Schedule the actor, filter with
  `postedWithinDays`, and pipe new rows to Slack, Google Sheets, Airtable,
  or your own database with Apify integrations, Make, n8n, or Zapier.
- **AI agents.** Every filter is a plain input field, so an agent can call
  this actor as a tool through the Apify MCP server and get clean JSON back.

### Sample output

One row per job:

```json
{
  "id": "greenhouse:stripe:8142952",
  "title": "Staff Software Engineer, Link - Bank Connections",
  "company": "Stripe",
  "companySlug": "stripe",
  "companyWebsite": "https://stripe.com",
  "ats": "greenhouse",
  "url": "https://stripe.com/jobs/search?gh_jid=8142952",
  "applyUrl": "https://stripe.com/jobs/search?gh_jid=8142952",
  "location": "San Francisco, Seattle, New York",
  "locations": ["San Francisco, Seattle, New York"],
  "workplaceType": null,
  "isRemote": null,
  "department": "Engineering",
  "team": null,
  "employmentType": null,
  "salaryMin": null,
  "salaryMax": null,
  "salaryCurrency": null,
  "salaryInterval": null,
  "salaryRaw": null,
  "postedAt": "2026-09-25T18:11:53.000Z",
  "updatedAt": "2026-09-25T20:44:56.000Z",
  "seniority": "staff_or_principal",
  "roleFamily": "software_engineering"
}
```

With `includeDescription` on, each row also carries `descriptionHtml` and
`descriptionText`.

Two fields are added by classification rather than copied from the company:
`seniority` (intern, junior, mid, senior, staff\_or\_principal, manager,
director\_or\_above) and `roleFamily` (software\_engineering, data, devops\_sre,
security, product, design, sales, marketing, customer, operations,
people\_finance\_legal, research, hardware, other). They are derived from the
title and department with a decision model, once per distinct title, and are
null for titles not yet classified.

Other fields are filled only when the company publishes them. Salary appears when
the job board exposes a pay range (common on Ashby and Lever, and on
Greenhouse boards with pay transparency turned on). Nothing is guessed.

### Input

| Field | What it does |
| --- | --- |
| `titleKeywords` | Keep jobs whose title contains any of these words or phrases. Case-insensitive. Empty means all titles. |
| `requireAllKeywords` | Require every keyword instead of any. |
| `excludeKeywords` | Drop jobs whose title contains any of these. |
| `locations` | Keep jobs whose location contains any of these strings, for example `London`, `Remote`, `New York`. |
| `remote` | `any`, `remote_only`, or `exclude_remote`, using the company's own remote flag or a "remote" location. |
| `departments` | Keep jobs whose department or team contains any of these. |
| `seniority` | Keep only these seniority levels, for example `["senior", "staff_or_principal"]`. |
| `roleFamilies` | Keep only these role families, for example `["software_engineering", "data"]`. |
| `companies` | Restrict to these companies by name or career-page slug. |
| `postedWithinDays` | Only jobs first published within this many days. |
| `maxResults` | Cap on returned jobs. Newest first. |
| `includeDescription` | Also fetch the full description for each returned job. |
| `boards` | Extra career boards to fetch live and search alongside the index, as `greenhouse:slug`, `lever:slug`, `ashby:slug`, or the board URL. |
| `source` | `index` (default) or `live` to search only the boards and companies you list. |

Example: remote senior or staff engineering roles posted this week.

```json
{
  "roleFamilies": ["software_engineering"],
  "seniority": ["senior", "staff_or_principal"],
  "remote": "remote_only",
  "postedWithinDays": 7,
  "maxResults": 500
}
```

Example: everything a specific set of companies has open right now, fetched
live.

```json
{
  "source": "live",
  "boards": ["greenhouse:stripe", "ashby:linear", "lever:palantir"],
  "includeDescription": true
}
```

### Pricing

Pay per event. You pay only for jobs returned, never for searching.

| Event | Price |
| --- | --- |
| Job returned | $0.004, so $4 per 1,000 jobs |
| Full description attached | $0.002, only when `includeDescription` is on |

**The first 5 jobs of every run are free**, so you can check the output
before paying for anything. Runs stop cleanly at your spending limit.

### How the index works

A maintainer schedule fetches every company in the bundled list from the
three job-board APIs, normalizes the jobs into one schema, and stores the
result in a public Apify key-value store. Your run reads that store, applies
your filters, and returns rows. The `OUTPUT` record of each run reports
`indexBuiltAt` so you know how fresh the data is.

If you need a company that is not in the index, add its board under `boards`
and it is fetched live in the same run. If you would rather keep a private
index of your own companies, run the actor with `mode: refresh`; it writes an
index into a key-value store in your account that you can then search with
`indexStoreId`.

### Limits and honesty

- Coverage is companies on Greenhouse, Lever, and Ashby. Workday and custom
  career sites are not included yet.
- Jobs are as fresh as the last index refresh, typically a few hours.
  Use `boards` with `source: live` when you need this minute.
- Location and remote fields are whatever the company wrote. Some companies
  put six cities in one string.
- Descriptions are fetched on demand from the source, so a company that
  removed a job between the refresh and your run returns no description for
  it, and you are not charged for it.

### About

Built and maintained by Kindling Labs. Issues and feature requests are
welcome on the Issues tab; they are read and answered.

# Actor input Schema

## `titleKeywords` (type: `array`):

Return jobs whose title contains any of these words or phrases (case-insensitive). Leave empty to match every title. Example: \["staff engineer", "principal engineer"].

## `requireAllKeywords` (type: `boolean`):

If enabled, a title must contain every keyword instead of any keyword.

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

Skip jobs whose title contains any of these words or phrases. Example: \["intern", "manager"].

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

Keep jobs whose location contains any of these strings, for example \["London", "Remote", "New York"]. Matched against every location listed on the job.

## `remote` (type: `string`):

Filter by remote status as declared by the company.

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

Keep jobs whose department or team contains any of these strings, for example \["Engineering", "Sales"].

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

Keep only these seniority levels, classified from the title: intern, junior, mid, senior, staff\_or\_principal, manager, director\_or\_above. Jobs whose title has not been classified yet are excluded when this filter is set.

## `roleFamilies` (type: `array`):

Keep only these role families, classified from the title and department: software\_engineering, data, devops\_sre, security, product, design, sales, marketing, customer, operations, people\_finance\_legal, research, hardware, other.

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

Restrict to these companies, matched against the company name or career-page slug. Leave empty to search every company in the index.

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

Only jobs first published within this many days. 0 means no limit.

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

Stop after this many jobs. Newest jobs come first. The first 5 results of every run are free.

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

Fetch the full job description (HTML and plain text) for every returned job. Charged per description.

## `boards` (type: `array`):

Career boards to fetch live and search alongside the index, as ats:slug, for example \["greenhouse:stripe", "lever:palantir", "ashby:linear"]. The slug is the last part of the company's boards.greenhouse.io, jobs.lever.co or jobs.ashbyhq.com URL.

## `source` (type: `string`):

'index' searches the pre-built index (plus any extra boards). 'live' fetches only the boards and companies you list, right now, without the index.

## `mode` (type: `string`):

'search' returns jobs. 'refresh' rebuilds an index into a key-value store in your own account from the bundled company list plus any extra boards; used by the maintainer's schedule, available to anyone who wants a private index.

## `indexStoreId` (type: `string`):

Key-value store that holds the index to search. Defaults to the public Kindling Labs index, refreshed every few hours.

## `indexStoreName` (type: `string`):

Name of the key-value store in your account to write the index into when mode is 'refresh'.

## `enrichTitles` (type: `boolean`):

In refresh mode, classify titles not yet in the cache using TypeSafe Jev. Needs the TYPESAFE\_API\_KEY environment variable on the actor.

## `maxNewTitles` (type: `integer`):

Caps classification work per refresh run.

## Actor input object example

```json
{
  "titleKeywords": [
    "software engineer"
  ],
  "requireAllKeywords": false,
  "remote": "any",
  "postedWithinDays": 0,
  "maxResults": 100,
  "includeDescription": false,
  "source": "index",
  "mode": "search",
  "indexStoreId": "YJp0mveg3fUevSVWe",
  "indexStoreName": "startup-jobs-index-v1",
  "enrichTitles": true,
  "maxNewTitles": 5000
}
```

# Actor output Schema

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

One row per matching job: title, company, location, remote status, department, seniority, role family, salary when published, dates, and links. Descriptions included when requested.

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

Counts of matched, returned and searched jobs, the index build time, and whether the run stopped at your spending limit.

# 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 = {
    "titleKeywords": [
        "software engineer"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kindlinglabs/startup-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 = { "titleKeywords": ["software engineer"] }

# Run the Actor and wait for it to finish
run = client.actor("kindlinglabs/startup-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 '{
  "titleKeywords": [
    "software engineer"
  ]
}' |
apify call kindlinglabs/startup-jobs-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kindlinglabs/startup-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/vHTYTUTDE8JRkf3o4/builds/mlhKMpa0kIffTQDg5/openapi.json
