# Company Jobs Feed: Greenhouse, Lever & Ashby (`fairquery/company-jobs-feed`) Actor

Live, open jobs straight from companies' own Greenhouse, Lever and Ashby boards via their official public APIs (no login, no proxies). Every row says when we verified it published, its age, long-open and repost signals. Filters, change tracking, de-dup, MCP. $0.99 per 1,000 jobs.

- **URL**: https://apify.com/fairquery/company-jobs-feed.md
- **Developed by:** [Fairquery](https://apify.com/fairquery) (community)
- **Categories:** Jobs, AI, Agents
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.99 / 1,000 job results

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

## Company Jobs Feed: Greenhouse, Lever & Ashby

**Live, open jobs straight from companies' own hiring systems, for $0.99 per 1,000 jobs plus $0.0005 per board scanned.** Optional extras: $0.00029 per job re-check and $0.0019 per company whose board we had to discover. Error rows, closed-job rows and rows merged by de-duplication are free. Example: Stripe + Ramp + Palantir (3 boards) filtered to 300 engineering jobs costs 300 × $0.00099 + 3 × $0.0005 = **$0.30**.

**Fairquery** Apify Actor that reads the **official public job-board APIs** of Greenhouse, Lever and Ashby (the applicant-tracking systems, or ATSs, that companies publish their openings from) and returns one clean row per job. Every row says **when we verified it as currently published** (`verified_open_at`), when the ATS says it was posted, **how old it is**, whether it looks **long-open** or **reposted**, and the exact API URL it came from. No login, no proxies, no browser.

> **What "open" means here.** A job is `open` when the employer's own ATS publishes it at the moment we fetch the board. That is the best public signal there is, but it is not proof of hiring intent: Greenhouse itself reports that 18–22% of jobs posted on its platform in a given quarter are ghost jobs. We give you age and repost signals so you can judge. We never claim a listing is "real" or "ghost-free".

### When to use this Actor (LLM / tool selection)

Use it when an agent or workflow needs to:

- Pull **every published job** from a list of companies (names, domains or board URLs) in one schema
- Search them by **title, location, country, remote, department, employment type, company, salary and posted-within**
- **Monitor** companies: return only **new or changed** jobs since the last run, plus free `closed` rows for jobs that disappeared
- Flag **stale listings**: `long_open` (older than 60 days, configurable) and `reposted` (same role, new job ID, within 90 days)
- Build **hiring-signal** lists for sales (Clay, n8n, Make) or feed **AI job-search agents** (career-ops-style scanners)

Do **not** use it for: LinkedIn, Indeed or Glassdoor listings (we never contact them), SmartRecruiters, Workday, Workable or Recruitee boards (not supported, see below), applying to jobs, or candidate/recruiter data.

### Live job listings straight from the company's ATS

Most employers publish openings through an applicant-tracking system. Greenhouse, Lever and Ashby each have a documented public job-board API, and this Actor reads **only those official APIs**. Every job you get is **currently published on the employer's own hiring system** at `verified_open_at`, not a copy on an aggregator.

That is not proof the role is being filled: Greenhouse reports that 18–22% of jobs on its own platform in a given quarter are ghost jobs. So every row also carries the signals you can measure from public data:

| Field | Meaning |
| --- | --- |
| `posted_at`, `age_days`, `age_basis` | ATS publish date (null if the ATS gives none; never guessed) and the age it implies |
| `long_open` | Open longer than `longOpenDays` (default 60). Evergreen roles trigger it too |
| `reposted`, `repost_of` | Same company + title + location as a job that closed in the last 90 days, under a new id (needs 2+ tracked runs) |
| `change`, `first_seen_at`, `last_seen_at` | Per-board history: `new`, `updated`, `unchanged`, `closed` |
| `open_check` | `detail_open` / `detail_closed` when you enable `confirmOpen` ($0.00029 per job) |

Use `excludeStale`, `maxAgeDays` and `postedWithinDays` to keep only fresh listings, and `onlyNew` with your own `stateStoreName` for "what's new since the last run".

**Measured on 2026-10-17:** Stripe (Greenhouse) 732 jobs, Palantir (Lever) 310 and Ramp (Ashby) 167, one request per board, about 1.5 s for all three. A filtered Stripe run (32 engineering jobs, 10 re-checks) took 3.1 s and cost $0.035.

Full guide with free Python code and MCP examples: https://fairquery.github.io/posts/live-job-listings-api-greenhouse-lever-ashby/

### Data sources

| ATS | Endpoint (documented, public, no key) | Documentation |
| --- | --- | --- |
| Greenhouse | `GET boards-api.greenhouse.io/v1/boards/{board}/jobs?content=true` | [Job Board API](https://developers.greenhouse.io/job-board.html): "publicly available, so authentication is not required for any GET endpoints" |
| Lever | `GET api.lever.co/v0/postings/{company}?mode=json` | [Postings API](https://github.com/lever/postings-api): "These jobs may be scraped by third parties" |
| Ashby | `GET api.ashbyhq.com/posting-api/job-board/{board}?includeCompensation=true` | [Public Job Posting API](https://developers.ashbyhq.com/docs/public-job-posting-api) |

Optional per-job re-checks use the documented detail endpoints (Greenhouse `/jobs/{id}`, Lever `/postings/{company}/{id}`). Ashby has no documented per-job endpoint, so its rows say `detail_not_supported`.

**Not included, and why:** **SmartRecruiters**: its API host's robots.txt allows only LinkedIn's crawler, so we don't call it (no SmartRecruiters host is ever contacted, not even during discovery). **Workday** has no documented public API. **Recruitee**'s Careers Site API now requires the employer's own token (calls without it will return 401 from 10 February 2027). **Workable** documents its public job endpoints only for an account owner's own careers page, not for third-party use. **LinkedIn** is never contacted.

### How it behaves (politeness and safety)

- Calls **only** the three ATS API hosts above, plus, for discovery only, pages on the **company website you supplied**.
- At most 2 parallel requests per host; Lever's `Crawl-delay: 1` is honoured. Identifying User-Agent `FairqueryJobsFeed/1.0`.
- **Kill switch:** any HTTP 403 or 429 (or an HTML block page where JSON was expected) stops all further requests to that host for the rest of the run. We never retry through other IPs or proxies.
- Careers pages: `robots.txt` is respected (5xx or unreachable robots.txt = we don't crawl), at most 3 pages per company, private/internal IP addresses and non-standard ports are refused, every redirect is re-checked.
- No login, no apply-page loads, no `POST`s, no applicant or recruiter data. Descriptions are optional and e-mail addresses and phone numbers are scrubbed from them by default.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `companies` | `string[]` | — | Names, domains or board URLs (max 200). Board URLs and companies in the starter list resolve for free |
| `boards` | `{ats,slug}[]` / `"ats:slug"` | — | Exact boards (max 500) |
| `useSeedList` | bool | `false` | Scan the verified starter list (51 company boards) |
| `discover` | bool | `true` | Find boards for unknown companies (careers page, then ≤6 API probes; cached 30 days) |
| `titleIncludes` / `titleExcludes` | `string[]` | — | Whole-word, case-insensitive, any-of |
| `titleRegex` | string | — | Optional, ≤200 chars |
| `locations` | `string[]` | — | Substring match on location text; `remote` also matches ATS remote flags |
| `countries` | `string[]` | — | ISO-2 codes (`US`, `GB`); structured where the ATS has it, else place-name list (`country_basis`) |
| `remote` | `any`|`only`|`exclude` | `any` | ATS remote flag where present, else location text (`remote_basis`) |
| `companyFilter`, `departments`, `employmentTypes` | `string[]` | — | Substring filters |
| `postedWithinDays` | int | — | ATS publish date at most N days ago (`includeUnknownPostedDate` keeps jobs without one) |
| `maxAgeDays`, `hasSalary`, `excludeStale`, `longOpenDays` | | `60` | Age, pay and stale-signal filters |
| `onlyNew` | bool | `false` | Only new/changed jobs since the last tracked run |
| `trackChanges`, `emitClosed`, `stateStoreName` | | `true`, `true`, `fairquery-jobs-feed-state` | History in a named key-value store in your account |
| `dedupe` | `exact`|`role`|`none` | `exact` | `role` merges one role listed per city into one row |
| `includeDescription` | `none`|`text` | `none` | Plain-text description (≤20,000 chars) |
| `scrubPII` | bool | `true` | Remove e-mails and phone numbers from descriptions |
| `confirmOpen` | `none`|`detail` | `none` | Re-check each emitted job on its detail endpoint ($0.00029 each, `maxOpenChecks` default 200) |
| `maxJobs` / `maxJobsPerBoard` | int | `5000` / `10000` | Spend and safety caps (max 50,000 / 20,000) |

#### Example input

```json
{
  "companies": ["stripe.com", "https://jobs.lever.co/palantir"],
  "boards": [{ "ats": "ashby", "slug": "ramp" }],
  "titleIncludes": ["engineer"],
  "titleExcludes": ["intern"],
  "countries": ["US"],
  "postedWithinDays": 30
}
```

### Output

Every row has `schema_version` (`1.0.0`), `type` (`job` | `board` | `company` | `error`) and `checked_at`.

#### Job row

```json
{
  "type": "job", "ats": "ashby", "board": "ramp", "company": "Ramp", "company_domain": "ramp.com",
  "job_id": "34413f8d-26bf-4bbc-8ade-eb309a0e2245", "job_key": "ashby:ramp:34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Security Engineer, Cloud", "department": "Engineering", "team": "Backend",
  "locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)", "Miami, FL"],
  "countries": ["US", "CA"], "country_basis": "ats", "remote": true, "remote_basis": "ats", "workplace_type": "hybrid",
  "employment_type": "Full-time",
  "compensation": { "summary": "$211.4K – $290.6K • Offers Equity", "min": 211400, "max": 290600, "currency": "USD", "interval": "1 YEAR" },
  "posted_at": "2026-04-07T17:12:35.753Z", "ats_updated_at": null,
  "status": "open", "open_check": "listed", "verified_open_at": "2026-10-17T22:46:37.060Z",
  "first_seen_at": "2026-10-17T22:46:37.060Z", "last_seen_at": "2026-10-17T22:46:37.060Z",
  "age_days": 193, "age_basis": "posted_at", "long_open": true, "reposted": false, "stale_signal": "long_open", "repost_of": null,
  "change": "new", "fingerprint": "646e3148231ba20d", "duplicate_group": null, "duplicate_of": null, "merged_job_ids": [],
  "apply_url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
  "job_url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "source_url": "https://api.ashbyhq.com/posting-api/job-board/ramp?includeCompensation=true",
  "description_text": null
}
```

**Freshness fields**

| Field | Meaning |
| --- | --- |
| `verified_open_at` | When this run saw the job published by the employer's ATS (or re-checked it with `confirmOpen`) |
| `posted_at` | The ATS's own publish/created time: Greenhouse `first_published`, Lever `createdAt`, Ashby `publishedAt`. `null` when absent, never guessed |
| `ats_updated_at` | Last-modified time where the ATS gives one (Greenhouse) |
| `first_seen_at` / `last_seen_at` | From your per-board history (named key-value store). On the first run both are now |
| `age_days`, `age_basis` | now − `posted_at`, or now − `first_seen_at` when there is no ATS date |
| `long_open`, `reposted`, `stale_signal` | Heuristics, labelled as such: older than `longOpenDays`; or a new job ID with the same company + title + location as a job that closed within 90 days (`repost_of`). Reposts are only detectable from the second tracked run |
| `change` | `new`, `updated`, `unchanged` or `closed` (free row: open last run, gone from a complete fetch now) |

**De-duplication.** The primary key is `job_key` = `ats:board:job_id`. `fingerprint` = sha1(company domain or name + normalized title + primary location) links the same role across boards or after an ATS migration: later rows get `duplicate_of`, and every multi-row group is listed in the `DUPLICATE_GROUPS` record. Nothing is silently dropped, except that `dedupe: "role"` merges one board's per-city copies into one row (`merged_job_ids`), and merged rows are free.

#### Board, company and error rows

- **Board row** (one per board scanned, `board-scanned`): `jobs_total`, `jobs_matched`, `jobs_emitted`, `complete`, `new`, `updated`, `closed`, `reposted`, `requests`, `discovered_via`.
- **Company row** (when discovery ran): `boards[]` with `via` (`careers_page` = high confidence, `slug_probe` = medium), `careers_pages_fetched`, `probes`, `cached`. Charged `company-discovered` only when discovery actually ran **and** found a board; cached lookups and failures are free.
- **Error rows** (free): `INPUT_EMPTY`, `INVALID_INPUT`, `INVALID_BOARD`, `INPUT_CAPPED`, `COMPANY_NOT_RESOLVED`, `NO_BOARD_FOUND`, `BOARD_NOT_FOUND`, `BOARD_TRUNCATED`, `BOARD_SKIPPED`, `HOST_BLOCKED`, `NETWORK_ERROR`, `ATS_HTTP_ERROR`, `ROBOTS_DISALLOWED`, `ROBOTS_UNAVAILABLE`, `URL_NOT_ALLOWED`, `DNS_FAILED`, `STATE_SAVE_FAILED`.

#### Run summary

Key-value store record **`RUN_SUMMARY`**: counts, events, requests per host, stopped hosts, echoed input.

### Pricing

| Event | Price | When |
| --- | --- | --- |
| `job-result` | $0.00099 | Each open job row delivered (after filters and de-duplication) |
| `board-scanned` | $0.0005 | Each board fetched successfully (even if no job matched your filters) |
| `open-confirmed` | $0.00029 | Each detail re-check, only with `confirmOpen: "detail"` |
| `company-discovered` | $0.0019 | Each company whose board we found by discovery (not for board URLs, starter-list companies or cached results) |

Error rows, `closed` rows and merged rows are **never** charged.

**Worked examples:** monitor 500 companies daily with `onlyNew`: 500 × $0.0005 = **$0.25 per check** plus $0.99 per 1,000 new jobs. Search the whole starter list (51 boards, ~13,400 listed jobs on 2026-10-17) for remote engineering roles posted in the last 14 days: 51 × $0.0005 + matching jobs × $0.00099.

To cap spend, set a maximum cost per run in the run options; the Actor stops cleanly before the limit and never delivers unpaid rows. `maxJobs` caps job rows per run.

### Memory / runtime

Default **256 MB** (max 512 MB), timeout 900 s. HTTP only (no browser, no proxy). Each Greenhouse, Lever or Ashby board is one request (0.1–2 s; Stripe's 732 jobs, Palantir's 310 and Ramp's 167 took about 1.5 s together). Very large Greenhouse boards (over 40 MB with descriptions, e.g. Anduril's ~2,500 jobs) are read from the lighter list without content.

### MCP / Apify tools

Call from agents via [Apify MCP](https://mcp.apify.com) with Actor `fairquery/company-jobs-feed`, or use the stdio MCP server [`@fairquery/company-jobs-feed-mcp`](https://www.npmjs.com/package/@fairquery/company-jobs-feed-mcp) (`npx -y @fairquery/company-jobs-feed-mcp`; free `jobs_get_board`, `jobs_find_board`, `jobs_check_open`, `jobs_list_seed_boards`, and paid `jobs_search_companies`).

```json
{
  "actor": "fairquery/company-jobs-feed",
  "input": { "companies": ["ramp.com"], "titleIncludes": ["engineer"], "remote": "only" }
}
```

### Troubleshooting

| Symptom | Cause and fix |
| --- | --- |
| Run fails at once with `INPUT_EMPTY` | No `companies`, `boards` or `useSeedList`. Console sample values are prefill only and are not sent with API calls |
| `NO_BOARD_FOUND` | The company doesn't use a supported ATS publicly, or its slug differs. Pass the board URL from its careers page (e.g. `https://jobs.lever.co/acme`) |
| `ROBOTS_DISALLOWED` / `ROBOTS_UNAVAILABLE` | The company's robots.txt doesn't allow us to read that careers page; we then only try API probes. Pass the board URL instead |
| `HOST_BLOCKED` | An ATS answered 403/429 and the kill switch stopped that host for this run. Rows are free; re-run later with fewer boards |
| `BOARD_TRUNCATED` | The board has more postings than `maxJobsPerBoard`; closed-job detection is skipped for that board |
| `RUN_BUDGET_REACHED` / `stopped_early: "max_cost_per_run"` | The run hit its **Max cost per run**. Nothing beyond it is charged; the partly delivered board is not billed and its history is not saved. Raise the limit or lower `maxJobs` |
| `BOARD_REDUCED_FIELDS` | A very large Greenhouse board (over 40 MB with descriptions) was read from the lighter documented list: jobs are complete, but department, offices and description are empty |
| `ROBOTS_UNAVAILABLE` on a site that works in a browser | Its robots.txt answers 5xx or redirects more than 5 times; RFC 9309 says not to crawl then. Pass the board URL |
| Everything is `change: "new"` | First tracked run (baseline). From the second run on you get `unchanged`/`updated`/`closed` |
| Wrong company matched | A `slug_probe` match (confidence `medium`) can be a different company with the same name; pass the exact board |

### FAQ

**Are these jobs real / not ghost jobs?**

We can show that a job is **currently published on the employer's own hiring system**, how old it is and whether it was reposted. We cannot prove the employer intends to hire. Use `excludeStale`, `maxAgeDays` and `postedWithinDays` to focus on fresh listings.

**Is this a LinkedIn scraper?**

No. We never contact LinkedIn. Many roles you see on job boards originate in these ATS feeds, so this is a rules-respecting way to get them first-hand.

**Why not Workday, Workable or Recruitee?**

We only use endpoints the ATS documents for public, third-party use. See "Not included, and why" above.

**Is there a global search over all companies?**

No global index: search covers the boards in your run. Set `useSeedList: true` to search the 51 verified starter boards, or bring your own list.

**How much does it cost?**

$0.99 per 1,000 jobs + $0.0005 per board, plus optional $0.00029 per re-check and $0.0019 per discovered company. Errors are free.

**Can an AI agent call it?**

Yes, through [Apify MCP](https://mcp.apify.com) with Actor `fairquery/company-jobs-feed`, or the stdio MCP server in the GitHub repo (the single-board tool runs free on your machine).

### Fairquery family

Other Fairquery tools on the Apify Store. Each one returns structured rows with machine-readable error codes, and error rows are never charged:

- [Email Validator for Agents (MX, Disposable, Typo)](https://apify.com/fairquery/email-validator-mx-disposable): syntax, MX, disposable, role and typo checks without sending mail; $0.79 per 1,000 emails
- [Website Contact & Company Enricher](https://apify.com/fairquery/website-contact-company-enricher): domain in, public business emails, phones, socials and company details out; $3 per 1,000 domains
- [Website Tech, SEO & llms.txt Check for Agents](https://apify.com/fairquery/website-tech-seo-ai-readiness): tech stack, SEO score, AI-crawler rules and llms.txt per site; $1.50 per 1,000 sites
- [Sanctions & Watchlist Screener (OFAC, EU, UK, UN)](https://apify.com/fairquery/sanctions-watchlist-screener): fuzzy name screening against official sanctions lists; $3 per 1,000 names + $0.007 per run
- [LLM Data Guard: PII & Secret Redactor](https://apify.com/fairquery/llm-data-guard-pii-secrets): redact PII and secrets before text reaches an LLM; $1.50 per 1,000 documents + $0.20 per 1,000 findings
- [Dependency Vulnerability Check for Agents](https://apify.com/fairquery/osv-dependency-vulnerability-check): packages and lockfiles against OSV.dev; $0.50 per 1,000 packages + $1.50 per 1,000 vulnerabilities
- [EDGAR Company Facts & Filings for Agents](https://apify.com/fairquery/edgar-company-facts-filings): SEC filings and XBRL facts by ticker, CIK or name; from $1.50 per 1,000 companies
- **MCP server** [`@fairquery/agent-trust-mcp`](https://www.npmjs.com/package/@fairquery/agent-trust-mcp): one MCP server for Claude Desktop, Cursor and VS Code (`npx -y @fairquery/agent-trust-mcp`).

All Fairquery Actors: https://apify.com/fairquery

### Disclaimer

Job data comes from each employer's public ATS job board and belongs to the employer. Fields are passed through as published (with light normalization); age, remote, country and stale signals are heuristics where labelled. No warranty that a listed job is still being hired for. Fairquery is not affiliated with Greenhouse, Lever, Ashby or LinkedIn.

### Links

- Apify Store: https://apify.com/fairquery/company-jobs-feed
- GitHub: https://github.com/fairquery/company-jobs-feed
- Contact: fairquery@proton.me
- License: MIT

# Actor input Schema

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

Company names, domains (ramp.com) or board URLs (https://jobs.lever.co/palantir). Seeded companies and board URLs resolve for free; others use discovery (see below). Max 200.

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

Exact boards: \[{"ats":"greenhouse|lever|ashby","slug":"..."}], or strings like "ashby:ramp" or board URLs. Optional company/domain per board. Max 500.

## `useSeedList` (type: `boolean`):

Add every board from the shipped starter list (51 verified company boards) so you can search them without bringing your own list. Each board is billed as one board scanned.

## `discover` (type: `boolean`):

For a company that is not a board URL or in the starter list: read up to 3 pages of its own website (only where robots.txt allows) for links to a supported ATS, then try up to 6 slug probes on the documented APIs. Results are cached for 30 days; cached lookups are free.

## `titleIncludes` (type: `array`):

Keep jobs whose title contains any of these words or phrases (case-insensitive, whole words: "engineer" does not match "Engineering").

## `titleExcludes` (type: `array`):

Drop jobs whose title contains any of these words.

## `titleRegex` (type: `string`):

Optional case-insensitive regular expression the title must match (max 200 characters, no nested quantifiers).

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

Substring match on the job's location text, e.g. "new york", "berlin". "remote" also matches jobs the ATS flags as remote.

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

ISO-2 codes like US, DE, GB. Uses the ATS's structured country where it has one (Lever, Ashby), otherwise a small place-name list on the location text (country_basis says which). Jobs with no country information are dropped when this is set.

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

only = remote jobs only; exclude = drop remote jobs. Uses the ATS's remote flag where present, otherwise the location text (remote_basis says which).

## `companyFilter` (type: `array`):

Keep only jobs whose company name or board slug contains any of these (useful with the starter list).

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

Substring match on department or team, e.g. "engineering", "sales".

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

Substring match on employment type, e.g. "full time", "contract", "intern".

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

Keep jobs whose ATS publish date (posted_at) is at most this many days ago. Jobs without a publish date are dropped unless 'Include unknown posted date' is on.

## `includeUnknownPostedDate` (type: `boolean`):

With 'Posted within', also keep jobs whose ATS gives no publish date.

## `maxAgeDays` (type: `integer`):

Drop jobs older than this. Age = now − posted_at, or now − first_seen_at when the ATS has no date (age_basis).

## `hasSalary` (type: `boolean`):

Keep only jobs where the ATS publishes compensation (mainly Ashby and some Lever boards).

## `excludeStale` (type: `boolean`):

Drop jobs flagged long_open (older than 'Long-open threshold') or reposted. These are heuristics, not proof either way.

## `longOpenDays` (type: `integer`):

A job older than this is flagged long_open.

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

Return only jobs that are new or changed since the last tracked run with the same state store (plus free 'closed' rows). The first run is the baseline: everything is new.

## `trackChanges` (type: `boolean`):

Keep per-board history (first_seen_at, last_seen_at, closed and repost detection) in a named key-value store in your account. Turn off to store nothing; history fields then describe this run only.

## `emitClosed` (type: `boolean`):

When tracking, emit one free row (change: closed) for each job that was open last run and is gone from a complete fetch now.

## `stateStoreName` (type: `string`):

Named key-value store for history and the discovery cache. Use a different name per saved search/monitor.

## `dedupe` (type: `string`):

exact = one row per ats+board+job id; role = also merge one board's rows with the same title + department into one row listing all locations (merged rows are free); none = no de-duplication. Every row carries a fingerprint and duplicate_of; nothing is silently dropped across boards.

## `includeDescription` (type: `string`):

text = plain-text job description (up to 20,000 characters).

## `scrubPII` (type: `boolean`):

Remove e-mail addresses and phone numbers (e.g. recruiter contacts) from descriptions.

## `confirmOpen` (type: `string`):

detail = re-fetch each emitted job from the ATS's documented per-job endpoint (Greenhouse, Lever; Ashby has none). A 404 marks it closed. $0.00029 per re-check; off by default because the board listing is already the employer's live list.

## `maxOpenChecks` (type: `integer`):

Cap on detail re-checks per run.

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

Stop after this many job rows (the main cost driver: $0.99 per 1,000).

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

Safety cap on postings read from one board. A truncated board skips closed detection.

## Actor input object example

```json
{
  "companies": [
    "stripe.com",
    "Ramp",
    "https://jobs.lever.co/palantir"
  ],
  "boards": [
    {
      "ats": "greenhouse",
      "slug": "stripe"
    }
  ],
  "useSeedList": false,
  "discover": true,
  "titleIncludes": [
    "engineer"
  ],
  "titleExcludes": [
    "intern"
  ],
  "remote": "any",
  "includeUnknownPostedDate": false,
  "hasSalary": false,
  "excludeStale": false,
  "longOpenDays": 60,
  "onlyNew": false,
  "trackChanges": true,
  "emitClosed": true,
  "stateStoreName": "fairquery-jobs-feed-state",
  "dedupe": "exact",
  "includeDescription": "none",
  "scrubPII": true,
  "confirmOpen": "none",
  "maxOpenChecks": 200,
  "maxJobs": 5000,
  "maxJobsPerBoard": 10000
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

## `duplicateGroups` (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 = {
    "companies": [
        "stripe.com",
        "https://jobs.lever.co/palantir"
    ],
    "boards": [
        {
            "ats": "ashby",
            "slug": "ramp"
        }
    ],
    "titleIncludes": [
        "engineer"
    ],
    "titleExcludes": [
        "intern"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fairquery/company-jobs-feed").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 = {
    "companies": [
        "stripe.com",
        "https://jobs.lever.co/palantir",
    ],
    "boards": [{
            "ats": "ashby",
            "slug": "ramp",
        }],
    "titleIncludes": ["engineer"],
    "titleExcludes": ["intern"],
}

# Run the Actor and wait for it to finish
run = client.actor("fairquery/company-jobs-feed").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 '{
  "companies": [
    "stripe.com",
    "https://jobs.lever.co/palantir"
  ],
  "boards": [
    {
      "ats": "ashby",
      "slug": "ramp"
    }
  ],
  "titleIncludes": [
    "engineer"
  ],
  "titleExcludes": [
    "intern"
  ]
}' |
apify call fairquery/company-jobs-feed --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fairquery/company-jobs-feed"
        }
    }
}
```

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/6R6gtNcaR2LlCEB5V/builds/yat30IN6Eb3Nu21cH/openapi.json
