# AI & Tech Jobs Finder | Greenhouse, Lever & Ashby (`reliable_quatrain/greenhouse-lever-ashby-jobs-hiring-signal`) Actor

Find AI and tech jobs across curated Greenhouse, Lever and Ashby employers. Filter roles and employment types; export CSV, RSS, JSON Feed and newsletter Markdown. Skip previously saved job keys.

- **URL**: https://apify.com/reliable_quatrain/greenhouse-lever-ashby-jobs-hiring-signal.md
- **Developed by:** [Trần Quốc Việt](https://apify.com/reliable_quatrain) (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

$1.50 / 1,000 matching job saveds

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

## AI & Data Jobs Finder | HiringSignal

![HiringSignal: selected employer feeds to a reviewable jobs shortlist](https://raw.githubusercontent.com/boyhoanghoi/hiring-signal-apify/main/assets/launch/jobs-finder.png)

Turn selected public employer job boards into a shortlist for your AI/Data
newsletter or niche job board. Choose a curated company group and role preset,
then download matching jobs as CSV, JSON, RSS, JSON Feed and a Markdown newsletter draft.
Greenhouse, Lever and Ashby feeds share one normalized schema with source links.

[Step-by-step first run](https://github.com/boyhoanghoi/hiring-signal-apify/blob/main/docs/FIRST_RUN.md) ·
[Hướng dẫn tiếng Việt](https://github.com/boyhoanghoi/hiring-signal-apify/blob/main/docs/FIRST_RUN_VI.md)

### Start with six matching jobs

```json
{
  "companyGroup": "ai",
  "rolePreset": "data-ai",
  "maxJobs": 6,
  "maxJobsPerBoard": 2
}
```

Run this small input, inspect the jobs and their application requirements, then
open **Output → Newsletter jobs shortlist (Markdown)** for a draft you can edit. This input saves
at most six matching jobs and at most two per board; availability changes, so it
may return fewer or zero. Six saved rows correspond to $0.009 in `job-saved`
events at the configured price. Check Pricing and the run's platform charges.

The watchlist is a starting point, not a complete AI-company directory, a global
job search or an endorsement by listed employers. The newsletter contains only
saved rows in provider order. It is not a newest-first ranking and is not posted
anywhere automatically.

### Pick your employer watchlist

`companyGroup` accepts `none`, `ai`, `developer-tools`, `fintech` or `all`.
The built-in catalog associates selected employer display names with public ATS
board keys. Inspect the current **EMPLOYER_CATALOG** output or run the standalone
CLI's `hiringsignal catalog` command for the actual roster and preset terms.
See the [catalog notes](https://github.com/boyhoanghoi/hiring-signal-apify/blob/main/docs/CATALOG.md)
for scope and verification context.

You can also supply `boards`:

```json
{
  "companyGroup": "none",
  "rolePreset": "engineering",
  "boards": ["greenhouse:stripe", "ashby:ashby"],
  "maxJobs": 6,
  "maxJobsPerBoard": 3,
  "knownJobIds": []
}
```

When both are supplied, explicit boards run first and the company group's boards
are added without duplicates. Use `companyGroup: "none"` for only your own
boards. The combined selection must contain 1–30 unique boards. To try only the
AI preset, clear any older explicit `boards` input.

Supported board forms include `greenhouse:stripe`, `lever:leverdemo`,
`lever-eu:yourboard`, `ashby:ashby`, and HTTPS public board URLs on
`boards.greenhouse.io`, `job-boards.greenhouse.io`, `jobs.lever.co`,
`jobs.eu.lever.co` and `jobs.ashbyhq.com`. Ashby capitalization is preserved.
Arbitrary API endpoints, custom careers domains and redirects are not accepted.

HiringSignal reads official public JSON feeds. It needs no ATS API key or proxy.
It does not submit applications, read private candidate data, bypass access
restrictions or support LinkedIn, Indeed or social-media scraping.

### Refine matching jobs

`rolePreset` accepts `all`, `data-ai`, `engineering`, `product-design` or
`sales-marketing`. Presets match case-insensitive title words or phrases from
the catalog; they are practical title filters, not an AI assessment of the role.
The `all` preset adds no role restriction.

Optional `includeTitleKeywords`, `excludeTitleKeywords` and `locationKeywords`
use case-insensitive substring matching. Terms within each list are OR; different
filter lists and the role preset combine with AND. Exclusion takes priority.
All filters apply **before saving and billing**.

`employmentTypes` is an optional OR list of `full-time`, `part-time`, `contract`,
`temporary`, `internship`, `freelance`, `apprenticeship` and `unknown`. An empty
list adds no restriction. `employmentTypeNormalized` maps only unambiguous
explicit provider labels such as Lever `categories.commitment` and Ashby
`employmentType`; the original label remains in `employmentType` and provenance
in `fieldSources`. Missing/ambiguous labels stay `unknown`. Greenhouse jobs
usually have no explicit employment type and are excluded when you select only
known types. The title and description never determine employment type.

`remoteOnly` requires an explicit provider remote field, excluding hybrid and
unknown jobs. A remote-looking title or location is not enough. Greenhouse's
list feed generally has no explicit workplace field, so its jobs usually do not
pass this filter. **Remote does not mean eligible to work from every country.**
Check each source posting's location, work authorization and application terms.

`publishedAfter` is an inclusive ISO date or timestamp. When set, jobs with an
unknown publication date are excluded. Greenhouse `updated_at` is a modification
date and never substitutes for publication. Lever public jobs generally lack a
documented publication date. Ashby `publishedAt` means **last published**, not
necessarily first posted. `publishedAtMeaning` preserves that distinction.

Defaults `companyGroup: "none"` and `rolePreset: "all"` preserve existing manual
watchlist workflows. The displayed starter input chooses AI/Data explicitly.

### Output you can use

The dataset has one row per normalized, filtered job actually saved:

| Fields | Meaning |
| --- | --- |
| `id`, `jobKey`, `source`, `boardKey` | Source ID and stable provider/board/job identity |
| `company`, `companySource` | Employer board slug and its source |
| `companyDisplayName`, `companyDisplayNameSource` | Curated display label when known, otherwise the board slug, with provenance |
| `title`, `locations`, `department`, `team` | Published job fields when available |
| `workplaceType`, `workplaceTypeSource` | Explicit remote/hybrid/onsite value or unknown |
| `employmentType`, `employmentTypeNormalized`, `descriptionText` | Original employment label, exact normalized label or unknown, and cleaned job content |
| `jobUrl`, `applyUrl` | Source job/application links when exposed |
| `publishedAt`, `publishedAtMeaning`, `updatedAt` | Publication and modification dates kept distinct |
| `salary` | Source compensation only; null when absent |
| `fieldSources`, `requestUrl`, `observedAt` | JSON field mapping, source API and observation time |

The Output tab also links spreadsheet-safe CSV, `newsletter.md`, `SUMMARY`,
`DIAGNOSTICS`, `KNOWN_JOB_IDS`, `EMPLOYER_CATALOG` and complete `OUTPUT` JSON.
The Markdown draft includes titles, employer labels, locations, workplace labels
and source application/job links. Review it before republishing.

**`jobs.rss` (RSS 2.0) and `jobs.feed.json` (JSON Feed 1.1)** export only the
saved rows, in provider order, with stable `jobKey` item IDs and source job
links. Missing source publication dates are omitted; observation time never
becomes a publication date. Ashby's last-publication meaning is labeled in the
content. Feed descriptions are shortened to 2,000 source description characters;
open the posting or use dataset JSON for more content. RSS source markup is
escaped and JSON Feed uses plain text. These are static per-run exports, not
an automatically updated hosted subscription or an unlimited job archive. See
the [integration guide](https://github.com/boyhoanghoi/hiring-signal-apify/blob/main/docs/FEED_INTEGRATION.md).

Diagnostics report per-board fetched/filtered/invalid/saved counts, errors and
completion flags. Summary signals use **only saved rows**, not total employer
hiring. A board name in the catalog does not guarantee its feed is currently
available or contains matching roles.

If every attempted employer feed fails, the run reports failure with
`sourceFailureFatal: true` after writing diagnostics, empty exports and unchanged
known IDs. A nonempty feed in which no listed job can be normalized is a source
schema failure. Successful empty feeds and zero filter matches are valid results;
mixed failures and successes remain partial and are visible in diagnostics.

### Repeat without paying for known keys

Copy the **KNOWN_JOB_IDS** JSON array into the next run's `knownJobIds`. Only
previous keys and jobs actually saved in this run enter this rolling union.
Filtered, failed or unpaid jobs are not marked seen. Keys are checked before
saving and billing.

Up to 10,000 keys are retained; the oldest are dropped and
`knownJobIdsTruncated` is true if the union grows larger. An evicted key can
appear again. This is a bounded seen-list, not an unlimited history database.
Scheduling the same input does not automatically copy state: your workflow must
save the new array and supply it in the following run.

An unknown key means **not in your supplied seen-list**, not newly published.
Previously saved jobs with changed descriptions remain skipped by key. An absent
job is never labeled closed. Capped, filtered, rate-limited or failed boards
cannot establish a full employer snapshot or hiring growth.

The standalone CLI can load a previous export directly:

```powershell
hiringsignal run --input examples/ai-data-newsletter.json --previous-ids output/known_job_ids.json --output output-next
```

The combined input seen-list must still fit the 10,000-key bound.

### Pricing and limits

The configured Actor fee is **$0.0015 per saved job ($1.50 per 1,000)**. The
Store Pricing tab controls current terms. In pay-per-event mode, `job-saved`
occurs once per successfully normalized, filtered, saved dataset row. Errors,
empty boards, filtered jobs and known keys do not generate this event. Check
displayed platform charges as well as the Actor price; infrastructure may still
be used when no row is saved. Your spending limit bounds persisted results.

Limits: 30 selected boards, 1,000 saved jobs per run, 1,000 per board, 8 MiB
decoded JSON per response, 80 HTTP attempts per run, 20 Lever pages per board,
5,000 processed source records per board and 12,000 description characters per
saved job. Transient requests receive at most one retry with paced requests and
a bounded retry wait. No JavaScript pages or private ATS endpoints are read.

Source fields may be absent or change. Salary is not estimated; currencies and
compensation intervals remain as published. Check source links and diagnostics
before making hiring or research decisions. Respect source content terms when
republishing jobs.

### Support

Open the Actor's Issues tab with the provider, public board, run ID, expected
field and observed result. Remove tokens and private data from reports.

# Actor input Schema

## `companyGroup` (type: `string`):

Small curated watchlists, checked 16 October 2026; availability may change. AI has 4 employers, developer tools 8, fintech 3; 14 distinct employers in all. Custom boards are added to this group. Choose Custom only to use just your boards.

## `rolePreset` (type: `string`):

Matches title words/phrases, not a skills classifier. Data/AI includes data, machine learning, ML, AI, artificial intelligence, analytics and scientist. Combined with your custom keyword/location filters using AND. No inferred seniority or remote eligibility.

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

Provider:slug entries or public board URLs. Custom boards come first, then the selected group; duplicates are removed. Maximum 30 combined boards. Example greenhouse:stripe. Choose a company group or enter at least one board. Ashby capitalization is retained.

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

Global cap after normalization, deduplication and filtering. Also bounded by your run spending limit.

## `maxJobsPerBoard` (type: `integer`):

Per-board saved job cap. Capped boards are marked partial; this is not a full hiring count.

## `includeTitleKeywords` (type: `array`):

Case-insensitive substring OR matching. Empty means all titles. Up to 30 terms.

## `excludeTitleKeywords` (type: `array`):

Case-insensitive substring exclusions, applied after the inclusion list. Up to 30 terms.

## `locationKeywords` (type: `array`):

Case-insensitive OR matching against published location labels. No inferred geography.

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

Exact normalized OR filter: full-time, part-time, contract, temporary, internship, freelance, apprenticeship, unknown. Empty means no restriction. Only explicit source labels are mapped; ambiguous/missing labels stay unknown. Greenhouse usually has no explicit employment label. No inference from title or description. Applies before saving and billing.

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

Requires a provider's explicit remote field. Greenhouse list feeds usually lack this and will be excluded; a remote-looking title is not enough. Hybrid is excluded.

## `publishedAfter` (type: `string`):

ISO date or timestamp, e.g. 2026-01-01. Jobs without a publication date are excluded when set. Greenhouse updated_at is not publication; Lever's public feed generally lacks publication dates. Ashby reports last publication.

## `knownJobIds` (type: `array`):

Paste KNOWN_JOB_IDS from a previous run. These stable jobKey values are skipped before saving/billing. Up to 10,000; the output retains the newest 10,000 if its rolling union grows larger.

## `timeoutSeconds` (type: `integer`):

Each request is retried at most once for transient failures.

## `minDelaySeconds` (type: `number`):

Requests are sequential and paced; rate-limit responses use a bounded retry delay. Long server retry requests fail safely.

## Actor input object example

```json
{
  "companyGroup": "ai",
  "rolePreset": "data-ai",
  "boards": [],
  "maxJobs": 6,
  "maxJobsPerBoard": 2,
  "includeTitleKeywords": [],
  "excludeTitleKeywords": [],
  "locationKeywords": [],
  "employmentTypes": [],
  "remoteOnly": false,
  "publishedAfter": "",
  "knownJobIds": [],
  "timeoutSeconds": 20,
  "minDelaySeconds": 0.5
}
```

# Actor output Schema

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

No description

## `csv` (type: `string`):

No description

## `newsletter` (type: `string`):

No description

## `rss` (type: `string`):

No description

## `jsonFeed` (type: `string`):

No description

## `employerCatalog` (type: `string`):

No description

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

No description

## `diagnostics` (type: `string`):

No description

## `knownJobIds` (type: `string`):

No description

## `report` (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 = {
    "companyGroup": "ai",
    "rolePreset": "data-ai",
    "maxJobs": 6,
    "maxJobsPerBoard": 2
};

// Run the Actor and wait for it to finish
const run = await client.actor("reliable_quatrain/greenhouse-lever-ashby-jobs-hiring-signal").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 = {
    "companyGroup": "ai",
    "rolePreset": "data-ai",
    "maxJobs": 6,
    "maxJobsPerBoard": 2,
}

# Run the Actor and wait for it to finish
run = client.actor("reliable_quatrain/greenhouse-lever-ashby-jobs-hiring-signal").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 '{
  "companyGroup": "ai",
  "rolePreset": "data-ai",
  "maxJobs": 6,
  "maxJobsPerBoard": 2
}' |
apify call reliable_quatrain/greenhouse-lever-ashby-jobs-hiring-signal --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,reliable_quatrain/greenhouse-lever-ashby-jobs-hiring-signal"
        }
    }
}
```

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/2VTBeCh22BK3amvDJ/builds/dtYBVUZGuucG79BNh/openapi.json
