company-jobs-feed
OfficialCompany 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 Actors: https://apify.com/fairquery. One MCP server for several of them: @fairquery/agent-trust-mcp.
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. WHEN: Before applying, alerting or contacting about a specific job you got earlier (job_key or ats + slug + jobId). 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: Free. RETURNS: One item {type:"open_check", ats, slug, job_id, status: open | closed, method: detail_endpoint | board_listing, checked_at, source_url} or an error item (HOST_BLOCKED, NETWORK_ERROR: retryable). PARAMETERS: Give either jobKey alone, or ats + slug + jobId; jobKey wins when both are given. EXAMPLE: {"jobKey":"greenhouse:stripe:8172503"} -> {"status":"open","method":"detail_endpoint"}.
| Name | Required | Description | Default |
|---|---|---|---|
| ats | No | Required. The applicant-tracking system hosting the board: "greenhouse" | "lever" | "ashby". Example: "ashby" | |
| slug | No | 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" | |
| jobId | No | Optional if jobKey is given. The ATS job id (job_id field). Example: "8172503" | |
| jobKey | No | 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" |
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?
Goes well beyond the annotations by explaining per-ATS mechanics (Greenhouse/Lever use a documented detail endpoint where HTTP 404 = closed; Ashby has no per-job endpoint so the board is fetched and searched, method 'board_listing'). It also flags retryable errors (HOST_BLOCKED, NETWORK_ERROR) and an important semantic caveat that 'open' means published, not actively hiring.
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 WHAT/WHEN/WHEN NOT/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE blocks are densely front-loaded with zero filler, and the one-line example anchors the calling convention. Verbose in raw length but every line carries unique information.
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?
Although an output schema exists (so returns need not be restated), the description nevertheless names the return fields and the failure modes, plus cost and ATS-specific behavior. An agent has everything needed to call it correctly and interpret the result.
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 each parameter is already documented, so the baseline is 3; the description earns above that by stating the either/or contract and the precedence rule ('jobKey wins when both are given'), which the schema does not express. It does not add format/syntax detail beyond the schema's examples.
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?
Starts with a specific verb+resource+scope ('Re-check ONE job on the employer's ATS right now') and explicitly reports the binary outcome (open/closed). It is immediately distinguishable from siblings like jobs_get_board and jobs_search_companies.
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?
Contains explicit WHEN ('before applying, alerting or contacting about a specific job you got earlier') and WHEN NOT sections that name the alternative tools and the condition that selects them (listing vs. bulk-checking with confirmOpen, paid). Nothing 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. COST: Free. RETURNS: Items of type "board": {ats, slug, company, domain, via (board_url | seed_list | careers_page | slug_probe), confidence (high | medium), board_url}. A slug_probe hit is "medium": the slug exists but may belong to a different company with the same name; check the jobs or pass the URL from the careers page. meta.notes lists robots/blocked/network notes. 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. 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 read-only, idempotent, non-destructive, open-world, so the bar is lower, yet the description still adds substantial behavior: the resolution order, robots.txt respect, <=3 careers pages, refusal of private/internal IPs, at most 6 ATS API GETs, stopping at first hit, and no caching/storage. This is far beyond the annotations; the only omission is rate-limit/backoff behavior.
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?
Well front-loaded with labeled WHAT/WHEN/WHEN NOT sections and a concrete example, and every section carries information. It is long, and 'COST: Free' duplicates the title's '(free)' label, a minor redundancy in an otherwise dense block.
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?
Despite an output schema existing, the RETURNS section adds value the schema cannot: the 'via' enum values, the confidence semantics, and specifically that a slug_probe hit is only 'medium' and may be the wrong company. Combined with cost, network constraints and meta.notes, an agent has everything needed to call and interpret this tool.
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 a baseline of 3 applies, but the PARAMETERS section adds genuinely new meaning: the three accepted input forms (board URL, domain/URL, plain name) trigger different execution paths, which the schema's flat string description does not convey. The worked example further anchors expected input/output.
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?
Opens with a specific verb+resource ('Resolve a company name, domain or careers/board URL to its public job board(s)') and immediately scopes it to Greenhouse/Lever/Ashby. It explicitly distinguishes itself from the siblings jobs_get_board and jobs_search_companies, so an agent can route 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 and WHEN NOT sections give explicit conditions and name the alternatives: use jobs_get_board when you already have ats+slug, jobs_search_companies for bulk paid lookups, and it states the ATSs it cannot find (Workday, Workable, Recruitee, LinkedIn). Nothing 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_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). No token, 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: Free. RETURNS: Paged job rows: job_key, title, company, department, team, locations, countries (+country_basis), remote (+remote_basis), workplace_type, employment_type, compensation (only when the ATS publishes it), posted_at (ATS date or null, never guessed), age_days + age_basis, long_open, verified_open_at, apply_url, job_url, source_url, fingerprint. meta: jobs_total on the board, jobs_matched, requests, complete. 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>. 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 the safety profile, but the description adds substantial context annotations cannot: local execution, one GET to a documented host, a 403/429 session kill-switch with HOST_BLOCKED, Lever's 1 s Crawl-delay spacing, no token/storage, and first_seen_at/change semantics. This is rich disclosure beyond the structured hints.
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?
Heavily structured and front-loaded with WHAT/WHEN/WHEN NOT, so an agent gets routing information immediately. It is dense and the RETURNS block partially restates fields that the output schema already carries, which is the main reason it is not 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?
For a 12-parameter read tool with full schema coverage and an output schema, this covers the remaining gaps: filter composition rules, paging volatility when the board is edited, supported/unsupported ATSs, and rate-limit behavior. Nothing needed to call it 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 PARAMETERS adds cross-parameter semantics not present in any single schema field: filters are AND-ed while values within a filter are OR-ed, titleIncludes matches whole words, countries drops unmatched rows, and postedWithinDays drops null-dated rows. Only slight overlap with the schema's per-field docs, hence 4 rather than 5.
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 names a specific verb+resource (fetch every job published on ONE company board from its official public ATS API) plus the scoping constraint (one board, supported ATS list). It explicitly distinguishes itself from jobs_search_companies (many companies/history) and jobs_find_board (board discovery).
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?
WHEN gives the precondition (you know ats + slug), WHEN NOT enumerates the alternatives and the exact tools to use instead (jobs_search_companies for scale/history, jobs_find_board for unknown boards), and it rules out unsupported ATSs explicitly.
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: Free. RETURNS: Paged items {type:"seed_board", company, domain, ats, slug, verified_at, verified_by, jobs_at_verification}; meta.by_ats counts. PARAMETERS: ats narrows the list; omit it for all three systems. 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 50 | |
| 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 already cover safety (readOnly, idempotent, non-destructive, closed-world), but the description adds substantive behavior beyond them: offline static list with no network call, the free cost, and the staleness caveat that jobs_at_verification is a snapshot from verified_at. That staleness warning is exactly the kind of non-obvious trait that prevents misuse.
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/PARAMETERS/EXAMPLE structure is front-loaded and scannable, with no wasted clauses. The RETURNS block partially restates the output schema, which is the only mildly redundant element.
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 zero-required-parameter, read-only list tool with an output schema and full annotation coverage, the description supplies everything an agent needs: scope, exclusions, chaining guidance, cost, and the staleness caveat. Nothing material 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 genuine meaning: 'ats narrows the list; omit it for all three systems' clarifies the default behavior, and 'feed ats + slug of any item straight into jobs_get_board' documents the intended chaining usage that the schema cannot express.
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') plus the scope of what was verified (official ATS APIs) and the fields returned. It is immediately distinguishable from jobs_get_board (live data) and jobs_find_board (resolution of arbitrary names).
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, or to know which names jobs_find_board resolves instantly') and WHEN NOT ('live job data (jobs_get_board) or companies not in the list (jobs_find_board)'), naming both alternatives and the condition selecting each.
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. maxJobs caps the job rows. 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, default "any" | |
| 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: drop long_open and reposted rows | |
| 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 (ATS publish date). 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 only provide readOnlyHint/openWorldHint/idempotentHint/destructiveHint, but the description adds a lot beyond them: requires APIFY_TOKEN, runs in the user's own Apify account, waits up to 300 s, persists history in a named key-value store, and documents politeness controls (ATS APIs only, 403/429 kill switch, robots.txt). Detailed pay-per-event cost breakdown and the 'each call starts a new billed run' caveat are disclosed. Nothing here contradicts the annotations.
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?
Front-loaded labeled sections (WHAT/WHEN/WHEN NOT/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE) make it skimmable, and for an 18-parameter paid tool every section earns its place. The cost and 'one run, fetch your pages' guidance prevent classic misuse.
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 the complexity (18 params, paid per-event billing, external Actor run), the description covers purpose, routing, auth, cost, behavior, return-row types, and a concrete example. An output schema exists, yet the RETURNS section still usefully summarizes row kinds. Nothing an agent needs to call it 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 3 is the baseline, but the description adds cross-parameter semantics the schema cannot express: at least one of companies/boards/useSeedList is required even though all are marked optional (else INPUT_EMPTY, unbilled), onlyNew depends on stateStoreName matching the prior run, and limit/offset page rows locally while every call incurs a new billed run. These are meaningful additions, though several per-parameter details (remote, postedWithinDays, excludeStale) 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 precise verb+resource: running the fairquery/company-jobs-feed Actor over many companies or boards, including discovery, filters, and history. It explicitly contrasts scope with siblings jobs_get_board (single board) and jobs_check_open (single job), so an agent can distinguish it 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 and WHEN NOT sections name the exact conditions and the free alternatives: use it for monitoring a company list, history/closed/repost signals, or many boards; avoid it for one board (jobs_get_board, free) or one job (jobs_check_open, free). No inference is required.
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.
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
Most tools have clearly distinct purposes: finding a board, fetching a board's jobs, checking one job, listing seed boards, and bulk monitoring. However, jobs_search_companies is a paid superset that functionally overlaps with jobs_get_board, jobs_find_board, jobs_check_open, and jobs_list_seed_boards, creating some potential for misselection despite explicit WHEN NOT guidance.
All five tool names use snake_case with a consistent jobs_ prefix and a verb_noun pattern (get_board, find_board, check_open, list_seed_boards, search_companies). The convention is predictable and readable throughout.
Five tools is well-scoped for a company jobs feed server, covering discovery, listing, single-job verification, seed reference, and paid bulk/history operations. Each tool earns its place without redundancy in count.
The set covers the main lifecycle: board discovery, live job retrieval, single-job open checks, seed reference, and paid history/monitoring. Minor gaps exist: no free single-job detail retrieval by jobKey, and history/closed/repost signals are only available through the paid tool.
Maintenance
Related MCP Connectors
Open roles from public Greenhouse, Lever, Ashby, Workable job boards as clean data. Winters Works.
Search 690k open jobs from official ATS feeds, and what changed since your last check.
Company hiring signals + open jobs from Greenhouse, Lever, Ashby boards, API + MCP. 20 free/day.
Live job openings from Workday, Greenhouse, Lever, Ashby, Workable and Personio career sites.
Related MCP Servers
- AlicenseAqualityBmaintenanceLive 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.3197 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.348 PyPI1MIT
- 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 gradedqualityBmaintenanceEnables 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.39 npmMIT