company-jobs-feed
OfficialUse this MCP server to find, list, filter, verify, and monitor live job postings from Greenhouse, Lever, and Ashby boards for AI agents or your own tooling.
Fetch all live jobs from one ATS board (Greenhouse, Lever, or Ashby) with filters for title, location, country, remote status, posted-within days, pagination, and optional PII-scrubbed descriptions.
Resolve a company name, domain, or careers URL to its ATS board (ats + slug) with discovery method and confidence.
Re-check whether a specific job is still open or closed on the employer’s ATS, optionally returning its full description if open.
List the bundled verified starter job boards offline, including company, domain, ATS, slug, and verification date.
Run a paid Apify Actor to search and monitor many companies or boards at once, with history (new/updated/closed jobs), repost detection, optional per-job re-checks, descriptions, and spend caps (requires APIFY_TOKEN).
Use the four free tools locally with no token; all tools are read-only. Supported sources are only Greenhouse, Lever, and Ashby.
Allows AI agents to query live job listings from Greenhouse job boards via the official Job Board API, including fetching all currently published jobs on a board, resolving companies to their Greenhouse board, checking whether a specific job is still open, and filtering jobs by title, location, country, remote status, and posting age.
This server has a remotely hosted endpoint. Open its connector listing for connection details, authentication requirements, and setup instructions:
Company Jobs Feed: live jobs from Greenhouse, Lever & Ashby (Apify Actor + MCP server)
Live job listings read straight from employers' own applicant-tracking systems through their official public job-board APIs, plus an MCP server so AI agents can use the same data. Every row says when it was verified as currently published on the employer's own hiring system, how old the posting is, whether it has been open a long time, and whether it looks like a repost.
Apify Actor:
fairquery/company-jobs-feed, pay per result: $0.99 per 1,000 jobs + $0.0005 per board scanned (+ $0.00029 per optional per-job re-check, $0.0019 per company found by discovery). Error, closed and merged rows are free.MCP server (
mcp/): four free tools that run on your machine with no token, and one paid tool that runs the Actor in your Apify account.
Honest scope: "currently published" is not "guaranteed to be hiring". The feed gives age, long-open and repost signals so you can filter likely stale or evergreen listings; it does not claim to be ghost-free.
Sources
ATS | API used | Included |
Greenhouse |
| Yes |
Lever |
| Yes |
Ashby |
| Yes |
SmartRecruiters | its API host's robots.txt allows only LinkedIn's crawler | No (never contacted) |
Recruitee | Careers Site API needs an employer token | No |
Workable | public endpoints are documented only for the account's own careers page | No |
Workday | no public job API | No |
not contacted at all | No |
Company names, domains and careers URLs can be resolved to boards (careers-page links with robots.txt respected, then at most 6 slug probes against the APIs). 51 verified starter boards ship in src/seed.ts.
Related MCP server: io.github.groundtruthtools/ats-jobs-mcp
MCP server
Install
Requires Node.js 20+. From npm: @fairquery/company-jobs-feed-mcp. Claude Desktop / Cursor / VS Code (mcpServers config):
{
"mcpServers": {
"fairquery-jobs-feed": {
"command": "npx",
"args": ["-y", "@fairquery/company-jobs-feed-mcp"],
"env": { "APIFY_TOKEN": "<only needed for jobs_search_companies>" }
}
}
}From source: git clone https://github.com/fairquery/company-jobs-feed && cd company-jobs-feed/mcp && npm ci --ignore-scripts && npm run build && node dist/index.js.
Docker: docker build -t fairquery-jobs-feed-mcp . && docker run -i --rm fairquery-jobs-feed-mcp.
Tools
Tool | Cost | What it does |
| free, local | All jobs currently published on one board (ats + slug), normalized and filtered by title, location, country, remote and posted-within. Returns |
| free, local | Resolve a company name, domain or careers URL to its Greenhouse/Lever/Ashby board, with |
| free, local | Re-check one job right now: |
| free, offline | The verified starter boards (company, domain, ats, slug, verified_at) |
| paid (Apify PPE) | Many companies at once with discovery, history (first/last seen, new/updated/closed), repost detection, optional re-checks and descriptions. Needs |
Every tool is read-only (readOnlyHint: true, destructiveHint: false), has an output schema, and describes every parameter. Descriptions follow a WHAT / WHEN / WHEN NOT / BEHAVIOR / COST / RETURNS / PARAMETERS / EXAMPLE layout. Without APIFY_TOKEN, initialize, tools/list and the four free tools work; the paid tool returns APIFY_TOKEN_MISSING.
Use the Actor from code
curl -X POST "https://api.apify.com/v2/acts/fairquery~company-jobs-feed/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \
-d '{"companies":["stripe.com","ramp.com"],"titleIncludes":["engineer"],"countries":["US"]}'import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('fairquery/company-jobs-feed').call({
companies: ['https://jobs.lever.co/palantir'],
countries: ['US'], titleIncludes: ['engineer'], postedWithinDays: 180,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();Input, output fields, pricing examples, troubleshooting and FAQ: .actor/README.md.
Example job row
Real row from a test run on 2026-10-17 (Ramp, Ashby; abbreviated):
{
"type": "job", "job_key": "ashby:ramp:34413f8d-26bf-4bbc-8ade-eb309a0e2245", "ats": "ashby", "board": "ramp",
"company": "Ramp", "title": "Security Engineer, Cloud", "department": "Engineering",
"locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)", "Miami, FL"], "countries": ["US", "CA"],
"remote": true, "workplace_type": "hybrid",
"posted_at": "2026-04-07T17:12:35.753Z", "age_days": 193, "age_basis": "posted_at", "long_open": true,
"verified_open_at": "2026-10-17T22:54:03.924Z", "first_seen_at": "2026-10-17T22:54:03.924Z", "change": "new", "reposted": false,
"compensation": { "summary": "$211.4K – $290.6K • Offers Equity", "min": 211400, "max": 290600, "currency": "USD", "interval": "1 YEAR" },
"apply_url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application", "fingerprint": "646e3148231ba20d"
}Politeness and security
Only the three documented API hosts are called (HTTPS, no redirects). Careers pages are read only for domains you supply, with robots.txt (RFC 9309), SSRF protection (private IPs refused, DNS pinned) and a 3-page cap. Any 403/429 stops that host for the run. No login, no proxies, no evasion. See SECURITY.md.
Development
See DEVELOPMENT.md. npm test (31 unit tests), npm run mcp:build && npm run mcp:test (MCP build + smoke test).
More from Fairquery
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): syntax, MX, disposable, role and typo checks without sending mail; $0.79 per 1,000 emails
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: 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): fuzzy name screening against official sanctions lists; $3 per 1,000 names + $0.007 per run
LLM Data Guard: PII & Secret Redactor: 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: packages and lockfiles against OSV.dev; $0.50 per 1,000 packages + $1.50 per 1,000 vulnerabilities
EDGAR Company Facts & Filings for Agents: SEC filings and XBRL facts by ticker, CIK or name; from $1.50 per 1,000 companies
Competitor Change Tracker: Pricing, Terms, Careers & Changelog: watch competitors' public pricing, terms, careers and changelog pages for real changes with plain-English summaries and webhook alerts; $0.00049 per page checked + $0.0049 per change
AI Exposure Scanner: Public Secrets, Files & AI-Crawler Policy: passive, scored scan of your own domains and public GitHub repos for leaked secrets (masked), exposed files, AI-crawler policy, security headers and SPF/DMARC; $0.039 per domain + $0.0039 per repo + $0.0019 per finding
MCP server
@fairquery/agent-trust-mcp: one MCP server for Claude Desktop, Cursor and VS Code (npx -y @fairquery/agent-trust-mcp). It has 8 free tools that need no token, plus paid tools that call the PII, email, sanctions and OSV Actors with your own Apify token.
All Fairquery Actors: https://apify.com/fairquery
License
MIT © 2026 Fairquery. Job data belongs to the employers. Not affiliated with Greenhouse, Lever, Ashby or LinkedIn.
Available Tools
5 toolsjobs_check_openRe-check whether one job is still published (free)ARead-onlyIdempotentInspect
WHAT: Re-check ONE job on the employer's ATS right now and report open or closed; optionally return that job's full row with its description. WHEN: Before applying, alerting or contacting about a specific job you got earlier (job_key or ats + slug + jobId), or to read one job's full details (includeDescription=true). WHEN NOT: Listing jobs (jobs_get_board) or checking many jobs at once (jobs_search_companies with confirmOpen, paid). BEHAVIOR: Greenhouse, Lever: one GET to the documented per-job endpoint; HTTP 404 = closed. Ashby has no per-job endpoint, so the whole board is fetched once and searched for the id (method "board_listing"). "open" means currently published by the employer; it does not prove the role is still being filled. COST: No APIFY_TOKEN, no billing. RETURNS: One "open_check" row (job = the full "job" row when includeDescription=true and the job is open, else null), or a tool error: INVALID_INPUT, BOARD_NOT_FOUND, HOST_BLOCKED or NETWORK_ERROR (the last two retryable). PARAMETERS: Give either jobKey alone, or ats + slug + jobId; jobKey wins when both are given. A malformed jobKey, or neither form complete, returns INVALID_INPUT without a request. An Ashby id costs one full board download (the same request as jobs_get_board); on Greenhouse and Lever a 404 triggers one extra board request so a closed job (status closed) is told apart from a missing board (BOARD_NOT_FOUND). includeDescription adds no request on Greenhouse and Lever (the per-job response already holds the text) and makes the Ashby board download include descriptions; description_text is plain text, <= 20,000 chars, e-mails/phones removed, exactly as in jobs_get_board. A closed job has no data left to return (job = null). EXAMPLE: {"jobKey":"greenhouse:stripe:8172503"} -> {"status":"open","method":"detail_endpoint"}.
| Name | Required | Description | Default |
|---|---|---|---|
| ats | No | Required unless jobKey is given. The applicant-tracking system hosting the board: "greenhouse" | "lever" | "ashby" (the first part of job_key). Example: "greenhouse" | |
| slug | No | Required unless jobKey is given. Board slug from the public board URL (boards.greenhouse.io/{slug}, jobs.lever.co/{slug}, jobs.ashbyhq.com/{slug}; the middle part of job_key), case-insensitive, 1-100 chars: a letter or digit, then letters, digits, ".", "_" or "-". Example: "stripe" | |
| jobId | No | Required unless jobKey is given. The ATS job id (job_id field, the last part of job_key), 1-100 chars: numeric on Greenhouse, a UUID on Lever and Ashby. Example: "8172503" | |
| jobKey | No | Optional if ats + slug + jobId are given; wins when both are given. The job_key field of a job row, format "{ats}:{slug}:{job_id}" with ats in lowercase (greenhouse | lever | ashby); any other shape returns INVALID_INPUT. Examples: "greenhouse:stripe:8172503", "lever:palantir:10dfc8bc-99ad-4ca2-ab76-853cb90a92c2" | |
| includeDescription | No | Optional boolean, default false. true adds job: the full job row (title, location, compensation, posted_at, description_text, ...) when the job is open. Example: true |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context: board totals, request counts, cost notes |
| count | Yes | Number of items in this page |
| items | Yes | Result rows. Each has "type": "job" (job_key, title, company, locations, countries, remote, posted_at, verified_open_at, age_days, long_open, apply_url, source_url, ...), "board", "company", "seed_board", "open_check" or "error" (code, message, retryable) |
| total | Yes | Total items after filters, before paging |
| offset | Yes | Offset of the first item in this page |
| hasMore | Yes | True if more items exist after this page |
| nextOffset | Yes | Offset to pass for the next page, or null when done |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/openWorld, but the description goes well beyond them: per-ATS request behavior (single GET on Greenhouse/Lever, full board download on Ashby via method "board_listing"), HTTP 404 meaning, the extra board request that disambiguates 'closed' from BOARD_NOT_FOUND, the enumerated error codes with which are retryable, and the cost statement (no APIFY_TOKEN, no billing). This is unusually rich behavioral disclosure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The WHAT/WHEN/WHEN NOT/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE scaffolding is highly scannable and the most decision-relevant content (WHAT, WHEN NOT) is front-loaded. It is however lengthy for a 5-parameter read tool, and the RETURNS and PARAMETERS blocks partially duplicate the output schema and input schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, yet the definition still covers the tricky cases an agent needs: closed jobs return job=null, Ashby has no per-job endpoint so behavior differs, errors are enumerated with retryability, and a concrete example call is given. Nothing material is missing for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already documents formats, the jobKey-wins precedence rule, and the INVALID_INPUT shape, so much of the PARAMETERS block is restatement. The description still adds genuine semantics the schema lacks: the malformed-key check happens with no request issued, includeDescription costs no extra request on Greenhouse/Lever but expands the Ashby download, and description_text limits (plain text, <=20,000 chars, contact info stripped).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and scope: 'Re-check ONE job on the employer's ATS right now and report open or closed', with an optional mode to fetch the full row. It explicitly distinguishes itself from the two closest siblings by name (jobs_get_board for listing, jobs_search_companies for bulk checks), so an agent can route correctly without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The WHEN line gives the triggering situations (before applying/alerting about a specific previously-fetched job, or reading one job's full details) and WHEN NOT names both alternatives with the condition that selects them, including that the bulk path is paid. Nothing about tool selection is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
jobs_find_boardFind which ATS board a company uses (free)ARead-onlyIdempotentInspect
WHAT: Resolve a company name, domain or careers/board URL to its public job board(s) on Greenhouse, Lever or Ashby. WHEN: Before jobs_get_board when you only know the company ("Ramp", "sentry.io", "https://stripe.com/jobs"). WHEN NOT: You already have ats + slug (call jobs_get_board). For many companies plus their jobs in one step use jobs_search_companies (paid). It cannot find Workday, Workable, Recruitee or LinkedIn pages. BEHAVIOR: Runs on your machine. Order: board-URL parsing -> verified starter list -> the company's own website (only for a domain/URL input; robots.txt respected, <= 3 pages, private/internal IPs refused) -> at most 6 GETs against the documented ATS APIs with slug guesses. Stops at the first hit. Nothing is cached or stored. Rate limits: max 2 parallel requests per host, Lever spaced 1 s apart, 15 s timeout per GET; ATS API network errors and HTTP 5xx are retried at most twice with backoff (0.5-1.6 s). Any HTTP 403/429 (or an HTML block page from an ATS API) stops that host for the session (kill switch, never retried, also for the other free tools): the call does not fail but adds a HOST_BLOCKED note to meta.notes, stops the careers-page step when the company site is blocked, and skips later slug probes on a stopped ATS host without sending them. COST: No APIFY_TOKEN and no billing; worst case about 4 website requests (robots.txt + 3 pages) and 6 ATS API GETs per call. RETURNS: "board" rows, or one NO_BOARD_FOUND error row. via (board_url | seed_list | careers_page | slug_probe) says how the board was found; confidence is "high" except for slug_probe hits ("medium": the slug exists but may belong to a different company with the same name, so check the jobs or pass the URL from the careers page). PARAMETERS: company accepts three forms with different behavior: a board URL is parsed with no network call; a domain or https URL enables the careers-page step; a plain name only uses the starter list and slug probes. Slug guesses for a name drop legal suffixes and "the" (Inc, LLC, Ltd, GmbH, Corp, Co, PLC, AG, SA, BV) and try the joined words, the hyphenated words and the first word; probes run Greenhouse, Lever, Ashby in that order and the first hit wins, so an ambiguous name returns at most one medium-confidence board. A starter-list match can return several boards (all high). EXAMPLE: {"company":"ramp.com"} -> [{"ats":"ashby","slug":"ramp","via":"seed_list","confidence":"high"}].
| Name | Required | Description | Default |
|---|---|---|---|
| company | Yes | Required. Company name, domain or URL, 2-300 chars. Examples: "Ramp", "sentry.io", "https://jobs.lever.co/palantir" |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context: board totals, request counts, cost notes |
| count | Yes | Number of items in this page |
| items | Yes | Result rows. Each has "type": "job" (job_key, title, company, locations, countries, remote, posted_at, verified_open_at, age_days, long_open, apply_url, source_url, ...), "board", "company", "seed_board", "open_check" or "error" (code, message, retryable) |
| total | Yes | Total items after filters, before paging |
| offset | Yes | Offset of the first item in this page |
| hasMore | Yes | True if more items exist after this page |
| nextOffset | Yes | Offset to pass for the next page, or null when done |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/openWorld/idempotent/non-destructive, yet the description adds rich context beyond them: local execution, the exact resolution order, robots.txt compliance, page/IP limits, per-host parallelism, Lever spacing, timeout, retry/backoff policy, and the 403/429 kill-switch semantics including the HOST_BLOCKED meta note. This far exceeds what annotations convey and is consistent with them.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Long but front-loaded with labeled sections (WHAT/WHEN/WHEN NOT/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE), so an agent can stop reading early. Nearly every sentence carries operational information; the EXAMPLE concretizes the return shape without waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With a complex networked tool, one input but many execution branches, this covers cost, failure modes, confidence semantics, and multi-board vs single-board outcomes. An output schema exists and returns are described compatibly rather than redundantly, leaving nothing an agent needs to call correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description goes well past the schema by mapping the three accepted input forms (board URL vs domain/URL vs plain name) to distinct behaviors, plus slug-guess rules (suffix stripping, word joining) and probe ordering. This is meaning the schema alone cannot supply.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The WHAT clause states a specific verb (resolve) and resource (company -> ATS board on Greenhouse/Lever/Ashby), and the WHEN/WHEN NOT sections name the exact siblings it differs from (jobs_get_board, jobs_search_companies). An agent can distinguish it from all five siblings without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit WHEN ('before jobs_get_board when you only know the company'), WHEN NOT (already have ats+slug; many companies + jobs in one step), and a hard exclusion list (cannot find Workday, Workable, Recruitee, LinkedIn). Routing is fully specified with no inference required.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
jobs_get_boardGet live jobs from one ATS board (free)ARead-onlyIdempotentInspect
WHAT: Fetch every job currently published on ONE company board from its official public ATS API (Greenhouse, Lever or Ashby), normalize it and filter it locally. WHEN: You know the board (ats + slug), e.g. from jobs_find_board or a careers-page link, and want its live openings now. WHEN NOT: Many companies at once, history (new/closed/reposted since last run), or careers-page discovery at scale: use jobs_search_companies (paid). Unknown board: use jobs_find_board first. SmartRecruiters, LinkedIn, Workday, Workable and Recruitee are not supported and never contacted. BEHAVIOR: Runs on your machine; one GET to the documented API host only (very large Greenhouse boards: a second, lighter GET). Nothing stored, no history: first_seen_at = now and change = null. Any HTTP 403/429 stops that host for the session (kill switch, HOST_BLOCKED error). Lever requests are spaced 1 s apart (its robots.txt Crawl-delay). COST: No APIFY_TOKEN, no billing. RETURNS: Paged "job" rows. compensation only when the ATS publishes it; posted_at is the ATS date or null (never guessed); age_basis says how age_days was derived. PARAMETERS: All filters are AND-ed; values inside one filter are OR-ed. titleIncludes/titleExcludes match whole words ("engineer" does not match "Engineering"). countries uses the ATS's structured country where present (Lever, Ashby) and a place-name list otherwise; rows without any country are dropped when countries is set. postedWithinDays drops rows with posted_at = null. Paging re-fetches the board, so pages from different calls can shift if the employer edits the board in between. EXAMPLE: {"ats":"ashby","slug":"ramp","titleIncludes":["engineer"],"remote":"only"} -> remote engineering roles at Ramp with salary ranges.
| Name | Required | Description | Default |
|---|---|---|---|
| ats | Yes | Required. The applicant-tracking system hosting the board: "greenhouse" | "lever" | "ashby". Example: "ashby" | |
| slug | Yes | Required. Board slug as it appears in the public board URL (case-insensitive): boards.greenhouse.io/{slug}, jobs.lever.co/{slug}, jobs.ashbyhq.com/{slug}; 1-100 chars: a letter or digit, then letters, digits, ".", "_" or "-". Example: "ramp" or "palantir" | |
| limit | No | Optional page size, integer 1-500, default 50 | |
| offset | No | Optional items to skip, integer >= 0, default 0; pass nextOffset from the previous response | |
| remote | No | Optional, "any" | "only" | "exclude", default "any" | |
| countries | No | Optional ISO-3166 alpha-2 codes, any-of, case-insensitive. Example: ["US","DE"] | |
| locations | No | Optional, 0-50 strings, any-of. Substring match on location text; "remote" also matches ATS remote flags. Example: ["new york","berlin"] | |
| longOpenDays | No | Optional integer, default 60: rows older than this get long_open = true (a heuristic, not proof of a ghost job) | |
| titleExcludes | No | Optional, 0-50 strings, any-of. Drop titles containing any of these. Example: ["intern","manager"] | |
| titleIncludes | No | Optional, 0-50 strings, any-of. Keep titles containing any of these whole words/phrases, case-insensitive. Example: ["engineer","data scientist"] | |
| postedWithinDays | No | Optional integer 1-3650: keep jobs whose ATS publish date is at most this many days old. Example: 14 | |
| includeDescription | No | Optional boolean, default false. true adds description_text (plain text, <= 20,000 chars, e-mails/phones removed) |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context: board totals, request counts, cost notes |
| count | Yes | Number of items in this page |
| items | Yes | Result rows. Each has "type": "job" (job_key, title, company, locations, countries, remote, posted_at, verified_open_at, age_days, long_open, apply_url, source_url, ...), "board", "company", "seed_board", "open_check" or "error" (code, message, retryable) |
| total | Yes | Total items after filters, before paging |
| offset | Yes | Offset of the first item in this page |
| hasMore | Yes | True if more items exist after this page |
| nextOffset | Yes | Offset to pass for the next page, or null when done |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover read-only/idempotent/open-world safety, but the description adds substantial context beyond them: runs locally, one GET to the documented host (a second for very large Greenhouse boards), nothing stored so first_seen_at=now and change=null, a HOST_BLOCKED kill switch on 403/429, and 1 s spacing on Lever per robots.txt. It also flags that paging re-fetches the board, so page windows can shift.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Labeled sections (WHAT/WHEN/WHEN NOT/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE) make it heavily front-loaded and skimmable, and every sentence carries operational content. It is nonetheless a dense block of text for a single-board fetch tool, where a slightly tighter PARAMETERS section would have sufficed.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 12-parameter, open-world read tool with an output schema, the description covers everything an agent needs: cost (no APIFY_TOKEN/billing), return shape (paged 'job' rows, compensation only when published, posted_at never guessed, age_basis), filter interaction rules, and failure modes. No meaningful gap remains.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema documents the fields, yet the PARAMETERS block adds cross-parameter meaning the schema cannot express: filters are AND-ed while values within a filter are OR-ed, titleIncludes/titleExcludes match whole words ("engineer" does not match "Engineering"), countries uses structured country where present and drops rows lacking one, and postedWithinDays drops rows with posted_at=null.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The WHAT line states a specific verb+resource+scope: fetch every job on ONE company board from its official public ATS API, then normalize and filter locally. It explicitly names the supported ATSs (Greenhouse, Lever, Ashby) and the unsupported ones, so an agent can distinguish it from siblings without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The WHEN/WHEN NOT blocks are explicit: use when you know ats+slug; use jobs_find_board when the board is unknown, and jobs_search_companies (paid) for many companies, history, or careers-page discovery at scale. It also names unsupported providers that are never contacted, removing ambiguity entirely.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
jobs_list_seed_boardsList the verified starter boards (free, offline)ARead-onlyIdempotentInspect
WHAT: List the bundled starter list of company job boards that Fairquery verified on their official ATS APIs (company, domain, ats, slug, verified_at, jobs_at_verification). WHEN: To pick companies to scan, or to know which names jobs_find_board resolves instantly. WHEN NOT: Live job data (jobs_get_board) or companies not in the list (jobs_find_board). The list is a starting point, not a global index of employers. BEHAVIOR: Offline: reads a static list compiled into the server; no network call. jobs_at_verification is a snapshot from verified_at and goes stale. COST: No APIFY_TOKEN, no billing. RETURNS: Paged "seed_board" rows in the fixed bundled order (grouped by ATS, so offsets are stable). PARAMETERS: ats narrows the list; omit it for all three systems. meta.by_ats always counts the whole list per ATS and ignores ats, so take paging totals from total. Feed ats + slug of any item straight into jobs_get_board. EXAMPLE: {"ats":"lever"} -> Palantir, Spotify, Zoox, Ro.
| Name | Required | Description | Default |
|---|---|---|---|
| ats | No | Optional filter: "greenhouse" | "lever" | "ashby"; default all | |
| limit | No | Optional page size, integer 1-500, default 100 | |
| offset | No | Optional items to skip, integer >= 0, default 0; pass nextOffset from the previous response |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context: board totals, request counts, cost notes |
| count | Yes | Number of items in this page |
| items | Yes | Result rows. Each has "type": "job" (job_key, title, company, locations, countries, remote, posted_at, verified_open_at, age_days, long_open, apply_url, source_url, ...), "board", "company", "seed_board", "open_check" or "error" (code, message, retryable) |
| total | Yes | Total items after filters, before paging |
| offset | Yes | Offset of the first item in this page |
| hasMore | Yes | True if more items exist after this page |
| nextOffset | Yes | Offset to pass for the next page, or null when done |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover the safety profile (readOnly, idempotent, non-destructive, closed-world), and the description adds genuinely new traits: fully offline with no network call, static list compiled into the server, jobs_at_verification is a freshness snapshot that goes stale, and no APIFY_TOKEN/billing required. That is real behavioral context beyond the structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Labeled sections (WHAT/WHEN/WHEN NOT/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE) are front-loaded with the highest-value routing information first. Dense but every block carries distinct content; no filler sentences.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return values need not be re-explained, and the description still adds the non-obvious paging caveat (stable offsets due to fixed bundled ATS-grouped order) and the downstream hand-off to jobs_get_board. Nothing an agent needs to call this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds meaning not in the schema: meta.by_ats always counts the whole list and ignores the ats filter, so paging totals must come from total; and ats+slug of any row feeds directly into jobs_get_board. The example clarifying that ats='lever' yields a specific set is useful, though limit/offset semantics are left to the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List the bundled starter list of company job boards ... verified on their official ATS APIs') and names the exact sibling tools it is not (jobs_get_board, jobs_find_board). An agent can distinguish this from live-data siblings without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit WHEN ('to pick companies to scan', 'to know which names jobs_find_board resolves instantly') and WHEN NOT (live data goes to jobs_get_board; unlisted companies go to jobs_find_board). It also frames the list's scope as 'a starting point, not a global index of employers', which prevents misuse.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
jobs_search_companiesSearch and monitor jobs across many companies (paid Apify Actor)ARead-onlyInspect
WHAT: Run the Fairquery Apify Actor fairquery/company-jobs-feed over many companies or boards: board discovery, search filters, per-board history (first_seen/last_seen, new/updated/closed, repost detection), optional per-job re-checks and descriptions. WHEN: Monitoring a company list ("what is new since yesterday?"), searching the verified starter list, or anything that needs history, closed/repost signals or more than a few boards. WHEN NOT: One board right now (jobs_get_board, free) or one job (jobs_check_open, free). BEHAVIOR: Read-only on job sites; requires APIFY_TOKEN and runs in YOUR Apify account (waits up to 300 s; history is kept in a named key-value store there). Same politeness rules as the free tools: documented ATS APIs only, 403/429 kill switch, robots.txt on careers pages. COST: Paid, pay-per-event, billed to your Apify account: $0.00099 per job row, $0.0005 per board scanned, $0.00029 per re-check (confirmOpen), $0.0019 per company found by discovery; no start fee. Error, closed and merged rows are free. Example: 3 boards, 300 jobs = about $0.30. RETURNS: Paged dataset rows: "job" rows (as jobs_get_board plus first_seen_at, last_seen_at, change, reposted, repost_of, stale_signal, duplicate_of), one "board" row per board (jobs_total, new, updated, closed), "company" rows for discovery, "error" rows (code, message, retryable). PARAMETERS: companies, boards and useSeedList: at least one is required although the schema marks all optional (none -> INPUT_EMPTY, not billed). onlyNew implies history and compares with the last run that used the same stateStoreName, so use one name per saved search; the first run returns everything as new. limit/offset page the returned rows locally; every call starts a new billed run, so fetch the pages you need from one result instead of re-calling. EXAMPLE: {"companies":["stripe.com","ramp.com"],"titleIncludes":["engineer"],"countries":["US"],"onlyNew":true}.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Optional page size, integer 1-500, default 50 | |
| boards | No | Optional. 0-500 exact boards. Example: [{"ats":"lever","slug":"palantir"}] | |
| offset | No | Optional items to skip, integer >= 0, default 0; pass nextOffset from the previous response | |
| remote | No | Optional, "any" | "only" | "exclude", default "any" (no remote filter). "only" keeps job rows with remote = true (an ATS remote flag, or remote wording such as "Remote" or "Anywhere" in the location or title; see remote_basis); "exclude" drops those rows and keeps rows where remote is false or unknown (null) | |
| maxJobs | No | Optional integer 1-50000, default 5000: spend cap on billed job rows | |
| onlyNew | No | Optional boolean, default false: only new/changed jobs since the last tracked run (plus free closed rows) | |
| companies | No | Optional (see PARAMETERS). 0-200 names, domains or board URLs. Example: ["stripe.com","Ramp","https://jobs.lever.co/palantir"] | |
| countries | No | Optional ISO-2 codes, any-of. Example: ["US","GB"] | |
| locations | No | Optional, 0-50 strings, any-of. Location substring match. Example: ["london"] | |
| confirmOpen | No | Optional boolean, default false: re-check each emitted job on its detail endpoint ($0.00029 each, max 200 per run) | |
| departments | No | Optional, 0-50 strings, any-of. Department/team substring match. Example: ["engineering"] | |
| useSeedList | No | Optional boolean, default false: also scan all verified starter boards (each billed as a board) | |
| excludeStale | No | Optional boolean, default false. true drops job rows with long_open = true (age_days > 60, counted from posted_at, or from first_seen_at when the ATS gives no date) or reposted = true (a new job id matching a job that closed on the same board within 90 days, from the history in stateStoreName, so never on a board's first tracked run). Free closed rows are not filtered by it | |
| titleExcludes | No | Optional, 0-50 strings, any-of. Whole-word title exclusion. Example: ["intern"] | |
| titleIncludes | No | Optional, 0-50 strings, any-of. Whole-word title match. Example: ["engineer"] | |
| stateStoreName | No | Optional, default "fairquery-jobs-feed-state": named key-value store for history in your Apify account | |
| postedWithinDays | No | Optional integer 1-3650, default off: keep only job rows whose ATS publish date (posted_at) is at most this many whole days old; rows without an ATS publish date (posted_at = null) are dropped. Free closed rows are not filtered by it. Example: 7 | |
| includeDescription | No | Optional boolean, default false: add plain-text descriptions (PII-scrubbed) |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context: board totals, request counts, cost notes |
| count | Yes | Number of items in this page |
| items | Yes | Result rows. Each has "type": "job" (job_key, title, company, locations, countries, remote, posted_at, verified_open_at, age_days, long_open, apply_url, source_url, ...), "board", "company", "seed_board", "open_check" or "error" (code, message, retryable) |
| total | Yes | Total items after filters, before paging |
| offset | Yes | Offset of the first item in this page |
| hasMore | Yes | True if more items exist after this page |
| nextOffset | Yes | Offset to pass for the next page, or null when done |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover safety (readOnly, non-destructive) and open-world/idempotency, but the description adds substantial beyond-annotation context: requires APIFY_TOKEN, runs in the caller's Apify account, waits up to 300 s, history lives in a named key-value store, politeness rules (ATS APIs only, 403/429 kill switch, robots.txt), and confirmation that each call is a new non-idempotent billed run. This is rich behavioral disclosure consistent with idempotentHint=false.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Organized into labeled sections (WHAT/WHEN/WHEN NOT/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE) that keep a long description scannable and front-load the purpose. It is dense and lengthy, but for an 18-parameter paid actor nearly every sentence carries required information; slightly over-budget keeps it from a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given a high-complexity, 18-parameter, paid tool, the description covers cost model with a worked example, return row types, killing constraints, and state semantics. An output schema exists so return detail is optional, yet the RETURNS section is included and sufficient; nothing needed to invoke correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds cross-parameter semantics the schema cannot express: at least one of companies/boards/useSeedList is required though all are marked optional (none -> INPUT_EMPTY), onlyNew implies history keyed to stateStoreName and the first run returns everything as new, and limit/offset page locally rather than re-invoking. This meaningfully compensates and goes well beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Run the Fairquery Apify Actor fairquery/company-jobs-feed over many companies or boards') with an explicit scope of board discovery, filters, and history tracking. It names the exact siblings it is not: 'jobs_get_board' and 'jobs_check_open', so an agent can route to it without opening another schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The WHEN section enumerates concrete triggers (monitoring a company list, 'what is new since yesterday?', anything needing history or closed/repost signals) and the WHEN NOT section names the two free siblings with their selecting condition. Alternatives and exclusions are fully explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v1.1.0- Changed
jobs_check_open1 field changed- added
Input schema / properties / includeDescriptionAdded value: +{ + "description": "Optional boolean, default false. true adds job: the full job row (title, location, compensation, posted_at, description_text, ...) when the job is open. Example: true", + "type": "boolean" +}
1 tool update
v1.0.6- Changed
jobs_list_seed_boards1 field changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Optional page size, integer 1-500, default 50"New value: +"Optional page size, integer 1-500, default 100"
3 tool updates
v1.0.5- Changed
jobs_check_open4 fields changed- changed
Input schema / properties / ats / descriptionPrevious value: -"Required. The applicant-tracking system hosting the board: \"greenhouse\" | \"lever\" | \"ashby\". Example: \"ashby\""New value: +"Required unless jobKey is given. The applicant-tracking system hosting the board: \"greenhouse\" | \"lever\" | \"ashby\" (the first part of job_key). Example: \"greenhouse\"" - changed
Input schema / properties / jobId / descriptionPrevious value: -"Optional if jobKey is given. The ATS job id (job_id field). Example: \"8172503\""New value: +"Required unless jobKey is given. The ATS job id (job_id field, the last part of job_key), 1-100 chars: numeric on Greenhouse, a UUID on Lever and Ashby. Example: \"8172503\"" - changed
Input schema / properties / jobKey / descriptionPrevious value: -"Optional if ats/slug/jobId are given. The job_key from a job row: \"<ats>:<slug>:<job_id>\". Example: \"lever:palantir:10dfc8bc-99ad-4ca2-ab76-853cb90a92c2\""New value: +"Optional if ats + slug + jobId are given; wins when both are given. The job_key field of a job row, format \"{ats}:{slug}:{job_id}\" with ats in lowercase (greenhouse | lever | ashby); any other shape returns INVALID_INPUT. Examples: \"greenhouse:stripe:8172503\", \"lever:palantir:10dfc8bc-99ad-4ca2-ab76-853cb90a92c2\"" - changed
Input schema / properties / slug / descriptionPrevious value: -"Required. Board slug as it appears in the public board URL (case-insensitive): boards.greenhouse.io/<slug>, jobs.lever.co/<slug>, jobs.ashbyhq.com/<slug>. Example: \"ramp\" or \"palantir\""New value: +"Required unless jobKey is given. Board slug from the public board URL (boards.greenhouse.io/{slug}, jobs.lever.co/{slug}, jobs.ashbyhq.com/{slug}; the middle part of job_key), case-insensitive, 1-100 chars: a letter or digit, then letters, digits, \".\", \"_\" or \"-\". Example: \"stripe\""
- Changed
jobs_get_board1 field changed- changed
Input schema / properties / slug / descriptionPrevious value: -"Required. Board slug as it appears in the public board URL (case-insensitive): boards.greenhouse.io/<slug>, jobs.lever.co/<slug>, jobs.ashbyhq.com/<slug>. Example: \"ramp\" or \"palantir\""New value: +"Required. Board slug as it appears in the public board URL (case-insensitive): boards.greenhouse.io/{slug}, jobs.lever.co/{slug}, jobs.ashbyhq.com/{slug}; 1-100 chars: a letter or digit, then letters, digits, \".\", \"_\" or \"-\". Example: \"ramp\" or \"palantir\""
- Changed
jobs_search_companies4 fields changed- changed
Input schema / properties / boards / items / properties / slug / descriptionPrevious value: -"Required. Board slug as it appears in the public board URL (case-insensitive): boards.greenhouse.io/<slug>, jobs.lever.co/<slug>, jobs.ashbyhq.com/<slug>. Example: \"ramp\" or \"palantir\""New value: +"Required. Board slug as it appears in the public board URL (case-insensitive): boards.greenhouse.io/{slug}, jobs.lever.co/{slug}, jobs.ashbyhq.com/{slug}; 1-100 chars: a letter or digit, then letters, digits, \".\", \"_\" or \"-\". Example: \"ramp\" or \"palantir\"" - changed
Input schema / properties / excludeStale / descriptionPrevious value: -"Optional boolean, default false: drop long_open and reposted rows"New value: +"Optional boolean, default false. true drops job rows with long_open = true (age_days > 60, counted from posted_at, or from first_seen_at when the ATS gives no date) or reposted = true (a new job id matching a job that closed on the same board within 90 days, from the history in stateStoreName, so never on a board's first tracked run). Free closed rows are not filtered by it" - changed
Input schema / properties / postedWithinDays / descriptionPrevious value: -"Optional integer 1-3650 (ATS publish date). Example: 7"New value: +"Optional integer 1-3650, default off: keep only job rows whose ATS publish date (posted_at) is at most this many whole days old; rows without an ATS publish date (posted_at = null) are dropped. Free closed rows are not filtered by it. Example: 7" - changed
Input schema / properties / remote / descriptionPrevious value: -"Optional, default \"any\""New value: +"Optional, \"any\" | \"only\" | \"exclude\", default \"any\" (no remote filter). \"only\" keeps job rows with remote = true (an ATS remote flag, or remote wording such as \"Remote\" or \"Anywhere\" in the location or title; see remote_basis); \"exclude\" drops those rows and keeps rows where remote is false or unknown (null)"
5 tool updates
v1.0.1- First observed
jobs_check_open - First observed
jobs_find_board - First observed
jobs_get_board - First observed
jobs_list_seed_boards - First observed
jobs_search_companies
TDQS
Scored across 5 tools
The five tools are cleanly separated along two axes: scope (one job / one board / a list of companies / static seed list) and cost (free local vs paid Apify). Each description includes explicit WHEN NOT clauses that route overlapping cases (e.g. jobs_check_open vs jobs_search_companies with confirmOpen, jobs_get_board vs jobs_find_board) to the correct tool.
All names use a uniform jobs_ prefix followed by a verb_noun pattern (check_open, find_board, get_board, list_seed_boards, search_companies). Snake_case throughout with no camelCase or style mixing; verbs are consistent and meaningful.
Five tools is well within the ideal range and each serves a distinct purpose: discovery, single-board listing, single-job verification, an offline seed list, and bulk paid monitoring. No redundant or filler tools; nothing feels missing or bloated.
The surface covers the full read-only lifecycle from board discovery (jobs_find_board) through listing (jobs_get_board) to per-job verification (jobs_check_open) and bulk historic monitoring (jobs_search_companies). Minor gap: history/change tracking is only available via the paid tool, so free-only users cannot detect closed/reposted jobs, and no non-ATS sources (Workday, Workable, LinkedIn) are supported, though this is a stated scope limit.
Maintenance
Related MCP Connectors
Live job postings from company career pages on Greenhouse, Lever, Ashby and SmartRecruiters.
Live jobs from 8 applicant tracking systems: Greenhouse, Lever, Workday, Ashby and four more.
1Open jobs from Greenhouse, Lever, Ashby and 4 more ATSs in one schema, with new-job monitoring.
Jobs from Greenhouse, Lever, Ashby, Workday, career pages, Google Jobs, SEEK, plus hiring signals.
Related MCP Servers
- AlicenseAqualityCmaintenanceLive tech-hiring intelligence for AI agents. Search 130K+ open jobs collected daily from ~500 tech companies' own career sites â plus company hiring profiles, tech stacks, salary benchmarks, and skill trends. Five tools work with no account.3171 npmMIT
- AlicenseAqualityBmaintenanceEnables querying open job postings directly from company applicant-tracking systems (Greenhouse, Ashby, Lever), finding a company's job board, listing and comparing roles, and accessing salary data, all without scraping or API keys.31MIT
- AlicenseAqualityCmaintenanceEnables real-time job search across thousands of companies' open roles from Greenhouse, Lever, Ashby, and SmartRecruiters, with full-text filtering and company-specific queries, no API key required.2MIT
- AlicenseNot gradedqualityFmaintenanceEnables users to look up a company's open job postings from public Greenhouse, Lever, and Ashby boards, returning them in one normalized shape with filtering by keyword, location, remote status, and posting date. It also identifies which of those three ATSs hosts a given company's board and how many roles are open there.24 npmMIT