Skip to main content
Glama

Fairquery Company Jobs Feed

Server Details

Live jobs from Greenhouse, Lever, Ashby, Personio, Pinpoint and Gem with age and repost signals.

If you are the author of this connector, you can claim ownership by verifying the domain or GitHub account it belongs to. Claimed connector authors can inspect health checks, view analytics, and manage their listing.
Status
Healthy
Last Tested
Transport
Streamable HTTP · MCP 2025-11-25
URL

TDQS

A4.7/5.0

Scored across 7 tools

Disambiguation4/5

Each tool targets a distinct resource/action pair: resolve board, fetch board, fetch one job, check one job's status, list the starter set, free location search, and paid multi-company run. The two edges that could be confused — jobs_check_open vs jobs_get_job, and jobs_search_by_location vs jobs_search_companies — are explicitly separated by WHEN/WHEN NOT clauses, but the heavy cross-referencing in descriptions shows the overlap is real enough that an agent must read carefully.

Naming Consistency5/5

All seven tools share a uniform jobs_ prefix followed by a verb_noun pattern (check_open, find_board, get_board, get_job, list_seed_boards, search_by_location, search_companies). No camelCase or style mixing, and verb choice maps cleanly to read-only semantics.

Tool Count5/5

Seven tools is a well-scoped surface for a job-feed server: discovery, board read, job read, status check, seed listing, free search, and paid monitoring each earn a distinct slot. The free/paid split is deliberate rather than redundant.

Completeness4/5

The surface covers the full lifecycle from company-name resolution through board listing, single-job detail, open/closed re-check, and historical monitoring with repost detection. Minor gaps remain — no free batch open-check across boards and Personio/Pinpoint/Gem boards cannot be discovered, only named — but these are documented scope limits with workarounds.

Available Tools

7 tools
jobs_check_openRe-check whether one job is still published (free)A
Read-onlyIdempotent
Inspect

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 you only need open or closed. WHEN NOT: One job's full detail (jobs_get_job). Listing a board (jobs_get_board). Checking many jobs at once (jobs_search_companies with confirmOpen, paid). BEHAVIOR: Runs on this Fairquery-hosted Worker (Cloudflare), not on the caller's machine; inputs leave the client. This Worker's code is designed not to store inputs or results. The job id and board slug are sent to the public ATS host in the requests below. Greenhouse, Lever: one GET to the documented per-job endpoint; HTTP 404 = closed. Ashby, Personio, Pinpoint and Gem have no per-job read here, so the documented board feed is fetched once and searched for the id (method "board_listing"). Personio is robots.txt then /xml. Pinpoint is robots.txt then /postings.json. Gem is GET /job_posts/ only. No apply URL is requested. "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: A malformed jobKey, or neither jobKey nor a complete ats + slug + jobId, returns INVALID_INPUT without a request. An Ashby, Personio, Pinpoint or Gem id costs one full board download; 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 and makes the Ashby or Personio board download include descriptions. Pinpoint and Gem description_text stay empty (the row links out). description_text is plain text, <= 20,000 chars, e-mails/phones removed, exactly as in jobs_get_board. EXAMPLE: {"jobKey":"greenhouse:stripe:8172503"} -> {"status":"open","method":"detail_endpoint"}.

NameRequiredDescriptionDefault
atsNoRequired unless jobKey is given. The applicant-tracking system hosting the board: "greenhouse" | "lever" | "ashby" | "personio" | "pinpoint" | "gem" (the first part of job_key). Example: "greenhouse"
slugNoRequired unless jobKey is given. Board slug from the public board URL (boards.greenhouse.io/{slug}, jobs.lever.co/{slug}, jobs.ashbyhq.com/{slug}, or the Personio account, Pinpoint subdomain or Gem vanity; the middle part of job_key), case-insensitive, 1-100 chars: a letter or digit, then letters, digits, ".", "_" or "-". A Personio account, Pinpoint subdomain or Gem vanity cannot contain a dot. Example: "stripe"
jobIdNoRequired unless jobKey is given. The ATS job id (job_id field, the last part of job_key), 1-100 chars: numeric on Greenhouse and Personio, a UUID on Lever and Ashby, the posting id on Pinpoint, the job post id on Gem. Example: "8172503"
jobKeyNoOptional 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 | personio | pinpoint | gem); any other shape returns INVALID_INPUT. Examples: "greenhouse:stripe:8172503", "personio:acme:4103", "gem:acme:am9icG9zdA"
includeDescriptionNoOptional boolean, default false. true adds job: the full job row (title, location, compensation, posted_at, description_text, ...) when the job is open. On Pinpoint and Gem, description_text stays empty. Example: true

Output Schema

NameRequiredDescription
metaNoTool-specific context: board totals, request counts, cost notes
countYesNumber of items in this page
itemsYesResult 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)
totalYesTotal items after filters, before paging
offsetYesOffset of the first item in this page
hasMoreYesTrue if more items exist after this page
nextOffsetYesOffset to pass for the next page, or null when done

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only, open-world and idempotency, yet the description adds substantially more: the tool runs on a hosted Worker with inputs leaving the client, code is designed not to store inputs/results, which ATS hosts get a per-job GET vs a full board feed, that a 404 means closed, robots.txt ordering, and the absence of billing. The caveat that "open" means published, not actively filled, is a disclosure no structured field carries.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Dense but well sectioned with WHAT/WHEN/WHEN NOT/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE and the example is front-loaded enough to anchor behavior. Some ATS-by-ATS detail in BEHAVIOR borders on verbose, but each sentence carries operational meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a five-parameter, zero-required tool with an output schema, the description still covers error taxonomy (INVALID_INPUT, BOARD_NOT_FOUND, HOST_BLOCKED, NETWORK_ERROR and which are retryable), the retry shape of the return value, and the privacy posture. Nothing an agent needs in order 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.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description earns more by explaining that jobKey wins over ats+slug+jobId, that a malformed key returns INVALID_INPUT with no request, and that an Ashby/Personio/Pinpoint/Gem id forces a full board download while includeDescription is free on Greenhouse and Lever. These are cost consequences of parameter choices that the schema cannot express. It stops short of 5 only because the per-field formats (UUID, numeric id) are already fully documented in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The WHAT line states a specific verb and scope: re-check ONE job on the employer's ATS right now and report open or closed. It explicitly contrasts with the sibling tools that do adjacent things (jobs_get_job, jobs_get_board, 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.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

WHEN and WHEN NOT are both spelled out, each with a named alternative, including the paid batch alternative (jobs_search_companies with confirmOpen). An agent can route without opening any other schema.

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)A
Read-onlyIdempotent
Inspect

WHAT: Resolve a company name, domain or careers/board URL to its public job board(s) on Greenhouse, Lever or Ashby. A Personio, Pinpoint or Gem URL (or personio:account, pinpoint:subdomain, gem:vanity) you already know is parsed on this Worker and not looked up. WHEN: Before jobs_get_board when you only know the company ("Ramp", "sentry.io", "https://stripe.com/jobs") or you have a Personio, Pinpoint or Gem career URL. WHEN NOT: The user asked for jobs in a place or by role and did not name a company. Call jobs_search_by_location. Do not ask them for a company and do not call this tool first. You already have ats + slug (call jobs_get_board). For many companies plus history use jobs_search_companies (paid). It cannot find Workday, Workable, Recruitee, Rippling or LinkedIn pages, and it does not discover Personio, Pinpoint or Gem boards. BEHAVIOR: Runs on this Fairquery-hosted Worker (Cloudflare), not on the caller's machine; inputs leave the client. This Worker's code is designed not to store inputs or results. Parts of the input are sent to public ATS hosts or the company site, as the steps below describe. Order: board-URL parsing (including personio:{account}, pinpoint:{subdomain}, gem:{vanity} and their public board URLs, no network) -> verified starter list -> the company's own website (only for a domain/URL input; robots.txt respected, <= 3 pages, private/internal IPs refused; Personio, Pinpoint and Gem hosts are not crawled) -> at most 6 GETs against the Greenhouse, Lever and Ashby APIs with slug guesses. Personio, Pinpoint and Gem are never probed and a careers-page link to them is ignored. 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 (including a Personio, Pinpoint or Gem URL, or personio:account / pinpoint:subdomain / gem:vanity) 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 (never Personio, Pinpoint or Gem) 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"}].

NameRequiredDescriptionDefault
companyYesRequired. Company name, domain or URL, 2-300 chars. Examples: "Ramp", "sentry.io", "https://jobs.lever.co/palantir"

Output Schema

NameRequiredDescription
metaNoTool-specific context: board totals, request counts, cost notes
countYesNumber of items in this page
itemsYesResult 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)
totalYesTotal items after filters, before paging
offsetYesOffset of the first item in this page
hasMoreYesTrue if more items exist after this page
nextOffsetYesOffset to pass for the next page, or null when done

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only/idempotent/open-world, but the description adds substantial context they cannot: execution on a Fairquery-hosted Worker, inputs leaving the client, robots.txt and private-IP handling, request caps (6 ATS GETs, <=3 pages), retry/backoff rules, and a session-wide kill switch that surfaces as HOST_BLOCKED in meta.notes rather than failing the call. The confidence semantics (high vs medium for slug_probe) is a behavioral caveat an agent must know to interpret results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded WHAT/WHEN/WHEN NOT/COST structure is excellent and each section earns its place. It is long and has minor redundancy — "does not store" is stated twice, and the Personio/Pinpoint/Gem exclusion is repeated in both WHEN NOT and BEHAVIOR.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with external network egress, a kill switch, and a heuristic that can return ambiguous results, the definition covers everything an agent needs: cost, request ceiling, what each `via` value means, and how to treat medium-confidence slug hits. The output schema exists, yet the RETURNS note still adds the interpretation guidance the schema can't.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

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 beyond the schema's "company name, domain or URL" by explaining that the three forms have materially different behavior: URL parsed offline, domain triggers the careers-page crawl, plain name uses seeds plus slug probes. It also documents the slug-derivation rules (legal suffixes stripped, joined/hyphenated/first-word variants) that determine whether a medium-confidence result appears.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource: "Resolve a company name, domain or careers/board URL to its public job board(s) on Greenhouse, Lever or Ashby." The scope (which ATSes, which input forms) is stated up front, so an agent can distinguish it from jobs_get_board and jobs_search_companies 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.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit WHEN (before jobs_get_board when only the company is known) and WHEN NOT sections name the alternative for each case: jobs_search_by_location for role/place queries, jobs_get_board when ats+slug is already known, jobs_search_companies for bulk/history. It also lists hard exclusions (Workday, Workable, Recruitee, Rippling, LinkedIn) and tells the agent not to ask the user for a company first.

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)A
Read-onlyIdempotent
Inspect

WHAT: Fetch every job currently published on ONE company board from its official public ATS API (Greenhouse, Lever or Ashby), from Personio's documented XML feed, from Pinpoint's documented postings.json, or from a named Gem job board, normalize it and filter it on this Worker. WHEN: You know the board (ats + slug), e.g. from jobs_find_board or a careers-page link, and want its live openings now. For Personio, Pinpoint and Gem you already know the company account, subdomain or vanity. WHEN NOT: One known job's full detail (jobs_get_job). Jobs in a place or by role across the starter list, without a company name (jobs_search_by_location). Do not ask the user for a company and do not call this tool or jobs_find_board first for that ask. Many companies at once, history (new/closed/reposted since last run), or careers-page discovery at scale: use jobs_search_companies (paid). Unknown Greenhouse/Lever/Ashby board: use jobs_find_board first. Personio, Pinpoint and Gem are never discovered. SmartRecruiters, LinkedIn, Workday, Workable, Recruitee and Rippling are not supported and never contacted. BEHAVIOR: Runs on this Fairquery-hosted Worker (Cloudflare), not on the caller's machine; inputs leave the client. This Worker's code is designed not to store inputs or results. The slug is sent to the public ATS host in the requests below. Greenhouse, Lever and Ashby: one GET to the documented API host only (very large Greenhouse boards: a second, lighter GET). Personio: robots.txt, then GET https://{account}.jobs.personio.de/xml only; all Personio accounts share one lane, one at a time, at least 1.5 s apart. Pinpoint: that tenant's robots.txt, then GET https://{subdomain}.pinpointhq.com/postings.json only (not jobs.json, not /api/v1); each subdomain is its own lane, one at a time, at least 1 s apart. Gem: GET https://api.gem.com/job_board/v0/{vanity}/job_posts/ only (no API key, no POST /applications); every vanity shares one lane, one at a time, at least 1 s apart. Pinpoint and Gem description_text stay empty (link out; the ad body is not copied). Gem job_url points at jobs.gem.com. Pinpoint job_url is the tenant listing link. Emails and phones are not kept from a Pinpoint posting. 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). A blocked Personio or Pinpoint host does not block other companies. A blocked Gem response stops api.gem.com for the session. 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.

NameRequiredDescriptionDefault
atsYesRequired. The applicant-tracking system hosting the board: "greenhouse" | "lever" | "ashby" | "personio" | "pinpoint" | "gem". Personio, Pinpoint and Gem are the account, subdomain or vanity you name, not a discovered board. Example: "ashby"
slugYesRequired. Board slug as it appears in the public board URL (case-insensitive): boards.greenhouse.io/{slug}, jobs.lever.co/{slug}, jobs.ashbyhq.com/{slug}, the Personio account in {account}.jobs.personio.de, the Pinpoint subdomain in {subdomain}.pinpointhq.com, or the Gem vanity in jobs.gem.com/{vanity}. Named feeds reject a dot. 1-100 chars. Example: "ramp" or "acme"
limitNoOptional page size, integer 1-500, default 50
offsetNoOptional items to skip, integer >= 0, default 0; pass nextOffset from the previous response
remoteNoOptional, "any" | "only" | "exclude", default "any"
countriesNoOptional ISO-3166 alpha-2 codes, any-of, case-insensitive. Example: ["US","DE"]
locationsNoOptional, 0-50 strings, any-of. Substring match on location text. US state names and codes match both ways (FL and Florida); NYC, SF, LA, DC, Tampa Bay and other US metro aliases match. "remote" also matches ATS remote flags. Example: ["new york","berlin"]
longOpenDaysNoOptional integer, default 60: rows older than this get long_open = true (a heuristic, not proof of a ghost job)
titleExcludesNoOptional, 0-50 strings, any-of. Drop titles containing any of these. Example: ["intern","manager"]
titleIncludesNoOptional, 0-50 strings, any-of. Keep titles containing any of these whole words/phrases, case-insensitive. Example: ["engineer","data scientist"]
postedWithinDaysNoOptional integer 1-3650: keep jobs whose ATS publish date is at most this many days old. Example: 14
includeDescriptionNoOptional boolean, default false. true adds description_text (plain text, <= 20,000 chars, e-mails/phones removed)

Output Schema

NameRequiredDescription
metaNoTool-specific context: board totals, request counts, cost notes
countYesNumber of items in this page
itemsYesResult 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)
totalYesTotal items after filters, before paging
offsetYesOffset of the first item in this page
hasMoreYesTrue if more items exist after this page
nextOffsetYesOffset to pass for the next page, or null when done

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare read-only, idempotent and open-world, so the safety bar is low, yet the description adds substantial context a caller could not infer: execution occurs on a remote Cloudflare Worker, inputs leave the client, per-host rate lanes (Personio 1.5s serialized, Gem/Pinpoint 1s), a 403/429 session kill switch per host, and that no history is stored (first_seen_at = now, change = null). Provider-specific data handling (Pinpoint/Gem description_text empty, emails/phones stripped) is disclosed too.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Strongly front-loaded and labeled (WHAT / WHEN / WHEN NOT / BEHAVIOR / COST / RETURNS / PARAMETERS / EXAMPLE), so an agent can stop reading early. It is nonetheless very dense for a tool description, with per-provider URL paths and robots.txt ordering that border on implementation detail rather than selection guidance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 12-parameter, six-provider, open-world network tool this covers everything needed to call it correctly: routing, auth/token absence (no APIFY_TOKEN, no billing), rate/block semantics, paging caveats, and return-field caveats (age_basis, never-guessed posted_at). An output schema exists, so it correctly avoids explaining row shape in depth.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

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 beyond it: filters AND across parameters and OR within, titleIncludes matches whole words so 'engineer' does not match 'Engineering', countries uses ATS structured country for Lever/Ashby and drops rows lacking a country, postedWithinDays drops null posted_at, and paging re-fetches the board so offsets can shift between calls. These are non-obvious semantics not expressed in the schema strings.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The WHAT line names the verb (fetch, normalize, filter) and the exact resource (all live jobs on ONE company ATS board) and enumerates the six supported providers. The title plus description make it distinguishable from jobs_get_job (single job detail) and jobs_search_by_location (cross-company) 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.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit WHEN ('you know the board (ats + slug)') and WHEN NOT sections name each sibling and the condition that routes to it: jobs_get_job, jobs_search_by_location, jobs_search_companies, jobs_find_board. It even states the negative instruction that Personio/Pinpoint/Gem boards are never discovered and that unsupported ATSs are never contacted.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jobs_get_jobGet one published job's full detail (free)A
Read-onlyIdempotent
Inspect

WHAT: Fetch the full detail of ONE job that is currently published on its official public ATS API (Greenhouse, Lever or Ashby), a Personio or Pinpoint feed, or a named Gem job board, normalized the same way as a board listing. WHEN: You already have a job_key, or ats + slug + jobId, from jobs_get_board or a board link and need that single job's text, location, compensation and dates. On Gem the row links to jobs.gem.com and does not include the ad body. WHEN NOT: Open or closed only (jobs_check_open). Every opening on a board (jobs_get_board). Jobs in a place across the starter list with no company name (jobs_search_by_location). Many companies, history or repost signals (jobs_search_companies, paid). SmartRecruiters, LinkedIn, Workday, Workable, Recruitee and Rippling are not supported and never contacted. BEHAVIOR: Runs on this Fairquery-hosted Worker (Cloudflare), not on the caller's machine; inputs leave the client. This Worker's code is designed not to store inputs or results. The job id and board slug are sent to the public ATS host in the requests below. Greenhouse and Lever: one GET to the documented per-job endpoint (the description is in that response). Ashby, Personio, Pinpoint and Gem have no per-job read here: the documented board feed is fetched once and the id is looked up. Personio is robots.txt then /xml. Pinpoint is robots.txt then /postings.json. Gem is GET /job_posts/ only. Pinpoint and Gem description_text stay empty. No apply URL is requested. HTTP 404/410 on Greenhouse or Lever, when the board itself exists, or an id missing from an Ashby, Personio, Pinpoint or Gem board, is JOB_CLOSED (closed or unknown id) with no partial row. Nothing is stored. Any HTTP 403/429 stops that host for the session (kill switch, HOST_BLOCKED). Lever requests are spaced 1 s apart. Personio accounts share one lane, at least 1.5 s apart. Each Pinpoint subdomain is at least 1 s apart. Gem vanities share one lane, at least 1 s apart. COST: No APIFY_TOKEN, no billing. RETURNS: One "job" row, or a tool error: INVALID_INPUT, BOARD_NOT_FOUND, JOB_CLOSED, HOST_BLOCKED or NETWORK_ERROR (the last two retryable). A non-JSON body or other HTTP failure uses the ATS client code (ATS_BAD_RESPONSE, ATS_HTTP_ERROR). PARAMETERS: A malformed jobKey, or neither jobKey nor a complete ats + slug + jobId, returns INVALID_INPUT with no request. An Ashby or Personio id downloads the whole board with descriptions. A Pinpoint or Gem id downloads the board list and still leaves description_text empty. Greenhouse and Lever use the per-job endpoint; a 404 or 410 triggers one extra board request so JOB_CLOSED is told apart from a missing board (BOARD_NOT_FOUND). Where a description is included it is plain text, at most 20,000 characters, with e-mail addresses and phone numbers removed. EXAMPLE: {"jobKey":"greenhouse:stripe:8172503"} -> that Stripe job's row.

NameRequiredDescriptionDefault
atsNoRequired unless jobKey is given. The applicant-tracking system hosting the board: "greenhouse" | "lever" | "ashby" | "personio" | "pinpoint" | "gem" (the first part of job_key). Example: "greenhouse"
slugNoRequired unless jobKey is given. Board slug from the public board URL (boards.greenhouse.io/{slug}, jobs.lever.co/{slug}, jobs.ashbyhq.com/{slug}, or the Personio account, Pinpoint subdomain or Gem vanity; the middle part of job_key), case-insensitive, 1-100 chars: a letter or digit, then letters, digits, ".", "_" or "-". A named Personio, Pinpoint or Gem slug cannot contain a dot. Example: "stripe"
jobIdNoRequired unless jobKey is given. The ATS job id (job_id field, the last part of job_key), 1-100 chars: numeric on Greenhouse and Personio, a UUID on Lever and Ashby, the posting id on Pinpoint, the job post id on Gem. Example: "8172503"
jobKeyNoOptional 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 | personio | pinpoint | gem); any other shape returns INVALID_INPUT. Examples: "greenhouse:stripe:8172503", "pinpoint:acme:118475"

Output Schema

NameRequiredDescription
metaNoTool-specific context: board totals, request counts, cost notes
countYesNumber of items in this page
itemsYesResult 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)
totalYesTotal items after filters, before paging
offsetYesOffset of the first item in this page
hasMoreYesTrue if more items exist after this page
nextOffsetYesOffset to pass for the next page, or null when done

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only cover read-only/idempotent/open-world, but the description discloses execution location (Cloudflare Worker), no-storage and PII-stripping behavior, per-host request patterns, throttle intervals, a 403/429 kill switch, and the exact error taxonomy. That is precisely the operational context annotations cannot express.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded WHAT/WHEN/WHEN NOT/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE headings make it scannable despite the density. A few statements repeat ('Nothing is stored', 'no partial row'), which is the only waste in an otherwise earned length.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, yet the description still enumerates success shape, all error codes, retryability, and the 20,000-char plain-text description cap. For a multi-provider federated read with rate limits and provider quirks, 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.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and already documents formats and examples, but the description adds decision-relevant rules: jobKey wins when both forms are supplied, malformed input returns INVALID_INPUT with no request, and an id-only call may download a whole board while Pinpoint/Gem still yield empty description_text. Slight over-length keeps it below 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (fetch the full detail of ONE published job) plus the exact scope of supported ATS sources. The supported/unsupported provider list makes it unmistakable which sibling calls would be redundant.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit WHEN and WHEN NOT blocks name the prerequisite (a job_key from jobs_get_board) and route each adjacent case to a named sibling: jobs_check_open, jobs_get_board, jobs_search_by_location, jobs_search_companies. It also states which providers are never contacted, removing any ambiguity.

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)A
Read-onlyIdempotent
Inspect

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 jobs on one board (jobs_get_board), a place search across this list (jobs_search_by_location), or companies not in the list (jobs_find_board). The list is a starting point, not a global index of employers. BEHAVIOR: Runs on this Fairquery-hosted Worker (Cloudflare), not on the caller's machine; inputs leave the client. This Worker's code is designed not to store inputs or results. No further network call: it reads a static list compiled into this Worker. 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 (original boards stay first, so earlier offsets stay put). 135 Greenhouse, Lever and Ashby boards. PARAMETERS: ats narrows the list; omit it for the whole starter list (Greenhouse, Lever, Ashby). Personio, Pinpoint and Gem are not in the starter list (named accounts only). 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, then more Lever boards.

NameRequiredDescriptionDefault
atsNoOptional filter: "greenhouse" | "lever" | "ashby" | "personio" | "pinpoint" | "gem"; default all. The starter list has no Personio, Pinpoint or Gem boards
limitNoOptional page size, integer 1-500, default 100
offsetNoOptional items to skip, integer >= 0, default 0; pass nextOffset from the previous response

Output Schema

NameRequiredDescription
metaNoTool-specific context: board totals, request counts, cost notes
countYesNumber of items in this page
itemsYesResult 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)
totalYesTotal items after filters, before paging
offsetYesOffset of the first item in this page
hasMoreYesTrue if more items exist after this page
nextOffsetYesOffset to pass for the next page, or null when done

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations, the description discloses that the tool runs on a hosted Cloudflare Worker, inputs leave the client, the Worker is designed not to store inputs or results, and no further network call is made. It also warns that jobs_at_verification is a stale snapshot and that no APIFY_TOKEN or billing is needed. These details materially help the agent understand execution and data freshness.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is labeled and front-loaded with WHAT, WHEN, and WHEN NOT before behavior, cost, returns, parameters, and an example. Although long, every section carries useful decision or invocation context, and there is no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers purpose, alternatives, execution environment, privacy behavior, cost, return shape, ordering, paging caveat, filtering semantics, and an example. With annotations and an output schema already present, this leaves no important gap for correct selection or invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, but the description adds operational meaning: ats narrows the starter list, does not include Personio/Pinpoint/Gem, and feed ats plus slug directly into jobs_get_board. It also clarifies a subtle paging caveat: meta.by_ats counts the whole list per ATS and ignores ats, so paging totals should come from total. This is meaningfully beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: listing bundled starter company job boards verified on official ATS APIs. It also describes the returned fields and distinguishes this tool from live job retrieval, location search, and board resolution siblings. An agent can identify exactly what this tool provides without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The WHEN and WHEN NOT sections explicitly say to use this tool to pick companies or identify instant board resolutions, and they name concrete alternatives for live jobs, location search, and companies outside the list. The description also frames the list as a starting point rather than a global index, 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_by_locationSearch the starter list by place and role (free, not every employer)A
Read-onlyIdempotent
Inspect

WHAT: Free search of the bundled starter list (135 public Greenhouse, Lever and Ashby boards). Not every employer and not every company. No company name. Live jobs from each board's official public API. Same normalized "job" rows as jobs_get_board. One call may cover only part of the list. WHEN: The user asks for jobs by place and/or role and does not name a company. Call THIS tool immediately. Do not ask the user for a company. Do not call jobs_get_board or jobs_find_board first. Examples: "avionics jobs in Baltimore MD", "avionics jobs in Costa Mesa", "remote RN jobs in PA", "nursing jobs in Maryland". Put the place in locations (Baltimore, Baltimore MD, MD, Maryland, BWI, PA, Costa Mesa) or countries, or set remote to "only". Put a skill or role that may not be the job title in descriptionIncludes (avionics, nurse), not in titleIncludes. WHEN NOT: The user already named one company or board (jobs_find_board, then jobs_get_board). They want history, closed jobs, reposts, or employers outside this starter list (jobs_search_companies, paid). This tool does not search all companies. BEHAVIOR: Runs on this Fairquery-hosted Worker (Cloudflare), not on the caller's machine; inputs leave the client. This Worker's code is designed not to store inputs or results. Each starter-list board slug is sent to its public ATS host. Requires a place signal or it returns INVALID_INPUT and does not fetch: a non-empty locations list, a non-empty countries list, or remote "only". Reads starter boards with the same client, kill switch and rate limits as the other free tools (at most 2 requests in flight per host, Lever spaced 1 s apart, up to 4 boards at a time). A board GET that fails before any job is read (network error, timeout, or HTTP 5xx) is retried once after about 300 ms and that retry is named in meta.notes. HTTP 403 and 429 are not retried. A second failure is skipped and named in meta.notes. The retry is skipped when the CPU budget, the wall clock, or the request cap is already spent. Greenhouse matches title and department on the lite list before any body request. A body is a second request to that same host only when a description keyword is still missing from the title and department, or includeDescription needs the text, and the lite list already has a job in the place. A Greenhouse board with more than 1000 jobs does not get a body request (the Anduril body is about 44 MB and does not fit the 128 MB isolate). Those boards are listed in meta.title_only_boards and return title and department matches only. Lever and Ashby descriptions are in the one list response. Each board is read in small slices, one job at a time. Description fields are not copied unless descriptionIncludes or includeDescription needs them. A text field longer than 20,000 characters is cut before matching and named in meta.notes. A board that is still too large is cut off and the scan continues. A missing board (BOARD_NOT_FOUND) is skipped. A board error is skipped and named in meta.notes. HOST_BLOCKED stops that host for the session; other hosts continue. The scan stops early on a 25 second wall clock, on the CPU work budget (12,000 ms estimated, a hard ceiling, 8,000 JSON characters per millisecond plus 0.08 ms per row built, because the hosted Worker clock does not advance during synchronous parsing; Paid Workers default to about 30 seconds of CPU and this soft-stop returns JSON before that; one call does not run unbounded), on a cap of 200 matched rows, or after 300 ATS requests (a hard ceiling: 135 starter boards take one lite request each, plus a Greenhouse body only when a description is still needed and the board has at most 1000 jobs, at most about 223 requests; Paid Workers allow 10,000 subrequests). It returns the jobs already matched (meta.partial true, meta.stop_reason "time_budget", "cpu_budget", "row_cap" or "subrequest_budget", meta.cost_usd 0). A scan error still returns that JSON (meta.stop_reason "error"); it does not throw a Server error and it does not invent jobs. Nothing is stored. offset pages rows inside this response. It does not continue the board walk. Pass boardCursor from meta.next_board_cursor, with the same place and keywords, to read boards this call did not start. next_board_cursor is null only when every board from the current board_cursor through the end was started, or no boards remain. Do not call again when it is null. A cpu_budget, subrequest_budget, time_budget, or row_cap stop before the end of the list sets it to the next unstarted index. A board error is skipped and named in meta.notes; it does not rewind the cursor behind boards this call already started. COST: No APIFY_TOKEN, no billing. meta.cost_usd is 0. RETURNS: Paged "job" rows. meta.boards_total, meta.boards_reached (boards this call started), meta.boards_scanned (boards that finished), meta.boards_ok, meta.boards_failed, meta.boards_skipped, meta.jobs_matched, meta.partial, meta.complete, meta.cut, meta.board_cursor, meta.next_board_cursor, meta.notes and meta.cost_usd. meta.stop_reason is set when partial is true. meta.title_only_boards lists Greenhouse boards whose body was skipped. meta.complete is true when this call started every board from board_cursor through the end and did not hit a budget. meta.next_board_cursor is the first starter index this call did not start, or null when nothing is left to resume. meta.cut is true when coverage is narrower than a full read. When jobs_matched is 0 after at least one board answered, meta.suggestions lists the title and description tokens tried, place aliases that had open roles, a sample of open titles in the requested place with the keyword removed, and a note that this list is not every employer and which ATS are covered. Suggestions are not job listings. PARAMETERS: At least one of locations, countries or remote "only" is required. There is no company parameter. titleIncludes and titleExcludes are whole words on the title only. descriptionIncludes is whole-word on the title, the department, or the scrubbed description, so "avionics" matches an Avionics department or Electrical Technician when the body says avionics. Do not put the same word in both titleIncludes and descriptionIncludes. "RN" matches RN, not "Registered Nurse". titleRegex is optional. postedWithinDays drops rows with no ATS date. includeDescription adds scrubbed plain text to the rows; descriptionIncludes still matches when it is false and does not add that text. Location terms expand US state names and codes both ways (MD and Maryland, PA and Pennsylvania) and metro aliases (NYC, SF, LA, DC, Tampa Bay, Baltimore / Baltimore MD / BWI). Two-letter codes match as whole tokens. countries stays ISO-2. boardCursor is an index into the starter list, not a row offset. EXAMPLE: {"locations":["Costa Mesa"],"descriptionIncludes":["avionics"]} -> starter-list jobs in Costa Mesa whose title, department or description says avionics. If meta.complete is false and meta.next_board_cursor is a number, call again with the same filters and {"boardCursor": that number}. No company.

NameRequiredDescriptionDefault
limitNoOptional page size, integer 1-500, default 50
offsetNoOptional items to skip, integer >= 0, default 0; pass nextOffset from the previous response
remoteNoOptional, "any" | "only" | "exclude", default "any". "only" is a place signal and keeps remote jobs. "exclude" alone does not start a scan.
countriesNoOptional ISO-3166 alpha-2 codes, any-of, case-insensitive. Counts as a place signal. Example: ["US"]
locationsNoOptional, 0-50 strings, any-of. Place names, any-of. US state names and codes match both ways (Baltimore MD, MD, Maryland, FL, Florida). Metro aliases include NYC, SF, LA, DC, Tampa Bay, and Baltimore / BWI. "remote" also matches ATS remote flags. At least one of locations, countries or remote "only" is required. This tool has no company parameter. Example: ["Tampa"]
titleRegexNoOptional case-insensitive regular expression the title must match (max 200 characters, no nested quantifiers). Example: "^senior\\b"
boardCursorNoOptional index into the starter list, integer >= 0, default 0. Pass meta.next_board_cursor from the previous response, with the same place and keywords, to continue boards that call did not start. This is not the row offset.
longOpenDaysNoOptional integer, default 60: rows older than this get long_open = true (a heuristic, not proof of a ghost job)
titleExcludesNoOptional, 0-50 strings, any-of. Drop titles containing any of these. Example: ["intern"]
titleIncludesNoOptional, 0-50 strings, any-of. Keep titles containing any of these whole words/phrases, case-insensitive. Title only. For a skill that may appear only in the posting, use descriptionIncludes instead. Example: ["engineer"]
postedWithinDaysNoOptional integer 1-3650: keep jobs whose ATS publish date is at most this many days old. Example: 14
includeDescriptionNoOptional boolean, default false. true adds description_text (plain text, <= 20,000 chars, e-mails/phones removed)
descriptionIncludesNoOptional, 0-50 strings, any-of. Whole words matched against the title, the department, or the scrubbed description, case-insensitive, any-of. Use this for a role or skill that may not be the job title ("avionics" matches an Avionics department, or Electrical Technician when the body says avionics). Do not also put the same word in titleIncludes. "RN" does not match "Registered Nurse". Greenhouse checks title and department on the lite list first. The row includes description_text only when includeDescription is true. Example: ["avionics"]

Output Schema

NameRequiredDescription
metaNoTool-specific context: board totals, request counts, cost notes
countYesNumber of items in this page
itemsYesResult 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)
totalYesTotal items after filters, before paging
offsetYesOffset of the first item in this page
hasMoreYesTrue if more items exist after this page
nextOffsetYesOffset to pass for the next page, or null when done

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only, open-world, idempotent, and non-destructive behavior, but the description adds substantial operational detail: partial results, stop reasons, retry rules, rate limits, cursor semantics, and that inputs leave the client. This goes well beyond what annotations provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with WHAT/WHEN/WHEN NOT headings, but the BEHAVIOR and RETURNS sections are extremely long and include many internal mechanics that may not be necessary for every invocation. It is structured and information-rich, but not concise.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity—distributed starter-list scanning, budgets, cursor continuation, and partial-result semantics—the description is complete enough for an agent to invoke it correctly. The output schema exists, so return values need not be fully explained, but the additional meta/cursor guidance reinforces correct usage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents all 13 parameters. The description still adds useful guidance beyond schema text, such as 'Do not put the same word in both titleIncludes and descriptionIncludes', location alias expansion, and that 'RN' does not match 'Registered Nurse'.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource ('Free search of the bundled starter list') and precisely scopes it ('Not every employer and not every company'). It explicitly distinguishes itself from siblings like jobs_get_board, jobs_find_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.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit WHEN and WHEN NOT sections, including the exact condition ('user asks for jobs by place and/or role and does not name a company') and alternatives for company-named queries. It also tells the agent not to call jobs_get_board or jobs_find_board first.

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)A
Read-only
Inspect

WHAT: Run the Fairquery Apify Actor fairquery/company-jobs-feed over many companies or boards: board discovery for Greenhouse, Lever and Ashby, a Personio, Pinpoint or Gem feed when you name it, 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). A place or role search of the starter list with no company name and no history (jobs_search_by_location — call that first; do not ask for a company). One job's full detail (jobs_get_job), or one open/closed re-check (jobs_check_open). BEHAVIOR: Read-only on job sites; requires APIFY_TOKEN. Called from this Fairquery-hosted Worker, which forwards your input and token to Apify and waits 20 s (APIFY_TIMEOUT if the run has not finished). The Actor runs in YOUR Apify account; history is kept in a named key-value store there. This Worker's code is designed not to store the input or token. Same politeness rules as the free jobs tools: documented ATS APIs only, Personio only at https://{account}.jobs.personio.de/xml after robots.txt, Pinpoint only at https://{subdomain}.pinpointhq.com/postings.json after robots.txt, Gem only at https://api.gem.com/job_board/v0/{vanity}/job_posts/ with no API key, 403/429 kill switch, robots.txt on careers pages. Personio, Pinpoint and Gem are not discovered. Pinpoint and Gem rows link out and do not copy the ad body. Output is that run's rows, not a public jobs directory. COST: Paid, pay-per-event, billed to your Apify account: $0.99 per 1,000 jobs + $0.50 per 1,000 boards, plus optional $0.29 per 1,000 re-checks (confirmOpen) and $1.90 per 1,000 discovered companies; 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 on this Worker; 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}.

NameRequiredDescriptionDefault
limitNoOptional page size, integer 1-500, default 50
boardsNoOptional. 0-500 exact boards. Example: [{"ats":"lever","slug":"palantir"}]
offsetNoOptional items to skip, integer >= 0, default 0; pass nextOffset from the previous response
remoteNoOptional, "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)
maxJobsNoOptional integer 1-50000, default 5000: spend cap on billed job rows
onlyNewNoOptional boolean, default false: only new/changed jobs since the last tracked run (plus free closed rows)
companiesNoOptional (see PARAMETERS). 0-200 names, domains or board URLs. Example: ["stripe.com","Ramp","https://jobs.lever.co/palantir"]
countriesNoOptional ISO-2 codes, any-of. Example: ["US","GB"]
locationsNoOptional, 0-50 strings, any-of. Location substring match. US state names and codes match both ways; NYC, SF, LA, DC and Tampa Bay metro aliases match. Example: ["london"]
confirmOpenNoOptional boolean, default false: re-check each emitted job on its detail endpoint ($0.29 per 1,000 re-checks, max 200 per run)
departmentsNoOptional, 0-50 strings, any-of. Department/team substring match. Example: ["engineering"]
useSeedListNoOptional boolean, default false: also scan all verified starter boards (each billed as a board)
excludeStaleNoOptional 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
titleExcludesNoOptional, 0-50 strings, any-of. Whole-word title exclusion. Example: ["intern"]
titleIncludesNoOptional, 0-50 strings, any-of. Whole-word title match. Example: ["engineer"]
stateStoreNameNoOptional, default "fairquery-jobs-feed-state": named key-value store for history in your Apify account
postedWithinDaysNoOptional 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
includeDescriptionNoOptional boolean, default false: add plain-text descriptions (PII-scrubbed)

Output Schema

NameRequiredDescription
metaNoTool-specific context: board totals, request counts, cost notes
countYesNumber of items in this page
itemsYesResult 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)
totalYesTotal items after filters, before paging
offsetYesOffset of the first item in this page
hasMoreYesTrue if more items exist after this page
nextOffsetYesOffset to pass for the next page, or null when done

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/openWorld/non-idempotent, and the description still adds substantial context beyond them: APIFY_TOKEN requirement, the 20 s Worker wait with APIFY_TIMEOUT, that the Actor runs in the caller's Apify account with history in a named key-value store, a documented 403/429 kill switch and robots.txt gating, and that every call starts a new billed run. The cost schedule is spelled out with a worked example.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The labeled WHAT/WHEN/WHEN NOT/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE structure is strongly front-loaded and scannable. It is long, and the politeness rules repeat the robots.txt condition three times with full URLs, which is the one place wording could be compressed without losing meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an 18-parameter, paid, open-world tool this covers everything an agent needs: invocation prerequisites, timeout behavior, billing model and caps, state semantics, error row shape, and a concrete request example. The return description is arguably redundant given the output schema exists, but it surfaces first_seen_at/change/repost fields that matter for interpreting results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although schema coverage is 100%, the PARAMETERS section adds meaning the schema cannot express: a cross-field constraint that at least one of companies/boards/useSeedList is required even though the schema marks all optional (none yields INPUT_EMPTY and is not billed), that onlyNew compares against the last run sharing the same stateStoreName so one name should be used per saved search, and that limit/offset page the Worker response rather than triggering new runs.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The WHAT clause names a specific verb and resource ('Run the Fairquery Apify Actor fairquery/company-jobs-feed over many companies or boards') and enumerates the scope: board discovery, named feeds, filters, per-board history. It explicitly differentiates itself from jobs_get_board, jobs_search_by_location, jobs_get_job and jobs_check_open.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Dedicated WHEN and WHEN NOT sections route the agent precisely: one board now goes to jobs_get_board, place/role search with no company name goes to jobs_search_by_location (with an explicit 'call that first; do not ask for a company' instruction), single job detail to jobs_get_job, re-check to jobs_check_open.

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. 1 tool update
    • Changedjobs_search_by_location2 fields changed
      • addedInput schema / properties / boardCursor
        Added value: +{
        +  "description": "Optional index into the starter list, integer >= 0, default 0. Pass meta.next_board_cursor from the previous response, with the same place and keywords, to continue boards that call did not start. This is not the row offset.",
        +  "maximum": 10000,
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • changedInput schema / properties / descriptionIncludes / description
        Previous value: -"Optional, 0-50 strings, any-of. Whole words matched against the title OR the scrubbed description, case-insensitive, any-of. Use this for a role or skill that may not be the job title (\"avionics\" matches Electrical Technician when the body says avionics). Do not also put the same word in titleIncludes. \"RN\" does not match \"Registered Nurse\". Reads the description from the same board request. The row includes description_text only when includeDescription is true. Example: [\"avionics\"]"New value: +"Optional, 0-50 strings, any-of. Whole words matched against the title, the department, or the scrubbed description, case-insensitive, any-of. Use this for a role or skill that may not be the job title (\"avionics\" matches an Avionics department, or Electrical Technician when the body says avionics). Do not also put the same word in titleIncludes. \"RN\" does not match \"Registered Nurse\". Greenhouse checks title and department on the lite list first. The row includes description_text only when includeDescription is true. Example: [\"avionics\"]"
  2. 1 tool update
    • Changedjobs_search_by_location3 fields changed
      • addedInput schema / properties / descriptionIncludes
        Added value: +{
        +  "description": "Optional, 0-50 strings, any-of. Whole words matched against the title OR the scrubbed description, case-insensitive, any-of. Use this for a role or skill that may not be the job title (\"avionics\" matches Electrical Technician when the body says avionics). Do not also put the same word in titleIncludes. \"RN\" does not match \"Registered Nurse\". Reads the description from the same board request. The row includes description_text only when includeDescription is true. Example: [\"avionics\"]",
        +  "items": {
        +    "maxLength": 100,
        +    "minLength": 1,
        +    "type": "string"
        +  },
        +  "maxItems": 50,
        +  "type": "array"
        +}
      • changedInput schema / properties / locations / description
        Previous value: -"Optional, 0-50 strings, any-of. Place names, any-of. US state names and codes match both ways (Tampa, FL, Florida); NYC, SF, LA, DC, Tampa Bay and other US metro aliases match. \"remote\" also matches ATS remote flags. At least one of locations, countries or remote \"only\" is required. Example: [\"Tampa\"]"New value: +"Optional, 0-50 strings, any-of. Place names, any-of. US state names and codes match both ways (Baltimore MD, MD, Maryland, FL, Florida). Metro aliases include NYC, SF, LA, DC, Tampa Bay, and Baltimore / BWI. \"remote\" also matches ATS remote flags. At least one of locations, countries or remote \"only\" is required. This tool has no company parameter. Example: [\"Tampa\"]"
      • changedInput schema / properties / titleIncludes / description
        Previous value: -"Optional, 0-50 strings, any-of. Keep titles containing any of these whole words/phrases, case-insensitive. Example: [\"engineer\"]"New value: +"Optional, 0-50 strings, any-of. Keep titles containing any of these whole words/phrases, case-insensitive. Title only. For a skill that may appear only in the posting, use descriptionIncludes instead. Example: [\"engineer\"]"
  3. 6 tool updates
    • Changedjobs_check_open6 fields changed
      • changedInput schema / properties / ats / description
        Previous value: -"Required unless jobKey is given. The applicant-tracking system hosting the board: \"greenhouse\" | \"lever\" | \"ashby\" | \"personio\" (the first part of job_key). Example: \"greenhouse\""New value: +"Required unless jobKey is given. The applicant-tracking system hosting the board: \"greenhouse\" | \"lever\" | \"ashby\" | \"personio\" | \"pinpoint\" | \"gem\" (the first part of job_key). Example: \"greenhouse\""
      • changedInput schema / properties / ats / enum
        Previous value: -[
        -  "greenhouse",
        -  "lever",
        -  "ashby",
        -  "personio"
        -]New value: +[
        +  "greenhouse",
        +  "lever",
        +  "ashby",
        +  "personio",
        +  "pinpoint",
        +  "gem"
        +]
      • changedInput schema / properties / includeDescription / description
        Previous value: -"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"New value: +"Optional boolean, default false. true adds job: the full job row (title, location, compensation, posted_at, description_text, ...) when the job is open. On Pinpoint and Gem, description_text stays empty. Example: true"
      • changedInput schema / properties / jobId / description
        Previous 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 and Personio, a UUID on Lever and Ashby. 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 and Personio, a UUID on Lever and Ashby, the posting id on Pinpoint, the job post id on Gem. Example: \"8172503\""
      • changedInput schema / properties / jobKey / description
        Previous 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 | personio); any other shape returns INVALID_INPUT. Examples: \"greenhouse:stripe:8172503\", \"personio:acme:4103\""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 | personio | pinpoint | gem); any other shape returns INVALID_INPUT. Examples: \"greenhouse:stripe:8172503\", \"personio:acme:4103\", \"gem:acme:am9icG9zdA\""
      • changedInput schema / properties / slug / description
        Previous 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}, or the Personio account; the middle part of job_key), case-insensitive, 1-100 chars: a letter or digit, then letters, digits, \".\", \"_\" or \"-\". A Personio account cannot contain a dot. Example: \"stripe\""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}, or the Personio account, Pinpoint subdomain or Gem vanity; the middle part of job_key), case-insensitive, 1-100 chars: a letter or digit, then letters, digits, \".\", \"_\" or \"-\". A Personio account, Pinpoint subdomain or Gem vanity cannot contain a dot. Example: \"stripe\""
    • Changedjobs_get_board4 fields changed
      • changedInput schema / properties / ats / description
        Previous value: -"Required. The applicant-tracking system hosting the board: \"greenhouse\" | \"lever\" | \"ashby\" | \"personio\". Personio is the company account you name, not a discovered board. Example: \"ashby\""New value: +"Required. The applicant-tracking system hosting the board: \"greenhouse\" | \"lever\" | \"ashby\" | \"personio\" | \"pinpoint\" | \"gem\". Personio, Pinpoint and Gem are the account, subdomain or vanity you name, not a discovered board. Example: \"ashby\""
      • changedInput schema / properties / ats / enum
        Previous value: -[
        -  "greenhouse",
        -  "lever",
        -  "ashby",
        -  "personio"
        -]New value: +[
        +  "greenhouse",
        +  "lever",
        +  "ashby",
        +  "personio",
        +  "pinpoint",
        +  "gem"
        +]
      • changedInput schema / properties / locations / description
        Previous value: -"Optional, 0-50 strings, any-of. Substring match on location text; \"remote\" also matches ATS remote flags. Example: [\"new york\",\"berlin\"]"New value: +"Optional, 0-50 strings, any-of. Substring match on location text. US state names and codes match both ways (FL and Florida); NYC, SF, LA, DC, Tampa Bay and other US metro aliases match. \"remote\" also matches ATS remote flags. Example: [\"new york\",\"berlin\"]"
      • changedInput schema / properties / slug / description
        Previous 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}, or the Personio account in {account}.jobs.personio.de (letters, digits, hyphens; a dot is rejected). 1-100 chars. Example: \"ramp\" or \"acme\""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}, the Personio account in {account}.jobs.personio.de, the Pinpoint subdomain in {subdomain}.pinpointhq.com, or the Gem vanity in jobs.gem.com/{vanity}. Named feeds reject a dot. 1-100 chars. Example: \"ramp\" or \"acme\""
    • Changedjobs_get_job5 fields changed
      • changedInput schema / properties / ats / description
        Previous value: -"Required unless jobKey is given. The applicant-tracking system hosting the board: \"greenhouse\" | \"lever\" | \"ashby\" | \"personio\" (the first part of job_key). Example: \"greenhouse\""New value: +"Required unless jobKey is given. The applicant-tracking system hosting the board: \"greenhouse\" | \"lever\" | \"ashby\" | \"personio\" | \"pinpoint\" | \"gem\" (the first part of job_key). Example: \"greenhouse\""
      • changedInput schema / properties / ats / enum
        Previous value: -[
        -  "greenhouse",
        -  "lever",
        -  "ashby",
        -  "personio"
        -]New value: +[
        +  "greenhouse",
        +  "lever",
        +  "ashby",
        +  "personio",
        +  "pinpoint",
        +  "gem"
        +]
      • changedInput schema / properties / jobId / description
        Previous 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 and Personio, a UUID on Lever and Ashby. 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 and Personio, a UUID on Lever and Ashby, the posting id on Pinpoint, the job post id on Gem. Example: \"8172503\""
      • changedInput schema / properties / jobKey / description
        Previous 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 | personio); any other shape returns INVALID_INPUT. Examples: \"greenhouse:stripe:8172503\", \"personio:acme:4103\""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 | personio | pinpoint | gem); any other shape returns INVALID_INPUT. Examples: \"greenhouse:stripe:8172503\", \"pinpoint:acme:118475\""
      • changedInput schema / properties / slug / description
        Previous 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}, or the Personio account; the middle part of job_key), case-insensitive, 1-100 chars: a letter or digit, then letters, digits, \".\", \"_\" or \"-\". A Personio account cannot contain a dot. Example: \"stripe\""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}, or the Personio account, Pinpoint subdomain or Gem vanity; the middle part of job_key), case-insensitive, 1-100 chars: a letter or digit, then letters, digits, \".\", \"_\" or \"-\". A named Personio, Pinpoint or Gem slug cannot contain a dot. Example: \"stripe\""
    • Changedjobs_list_seed_boards2 fields changed
      • changedInput schema / properties / ats / description
        Previous value: -"Optional filter: \"greenhouse\" | \"lever\" | \"ashby\" | \"personio\"; default all. The starter list has no Personio boards"New value: +"Optional filter: \"greenhouse\" | \"lever\" | \"ashby\" | \"personio\" | \"pinpoint\" | \"gem\"; default all. The starter list has no Personio, Pinpoint or Gem boards"
      • changedInput schema / properties / ats / enum
        Previous value: -[
        -  "greenhouse",
        -  "lever",
        -  "ashby",
        -  "personio"
        -]New value: +[
        +  "greenhouse",
        +  "lever",
        +  "ashby",
        +  "personio",
        +  "pinpoint",
        +  "gem"
        +]
    • Addedjobs_search_by_location
    • Changedjobs_search_companies4 fields changed
      • changedInput schema / properties / boards / items / properties / ats / description
        Previous value: -"Required. The applicant-tracking system hosting the board: \"greenhouse\" | \"lever\" | \"ashby\" | \"personio\". Personio is the company account you name, not a discovered board. Example: \"ashby\""New value: +"Required. The applicant-tracking system hosting the board: \"greenhouse\" | \"lever\" | \"ashby\" | \"personio\" | \"pinpoint\" | \"gem\". Personio, Pinpoint and Gem are the account, subdomain or vanity you name, not a discovered board. Example: \"ashby\""
      • changedInput schema / properties / boards / items / properties / ats / enum
        Previous value: -[
        -  "greenhouse",
        -  "lever",
        -  "ashby",
        -  "personio"
        -]New value: +[
        +  "greenhouse",
        +  "lever",
        +  "ashby",
        +  "personio",
        +  "pinpoint",
        +  "gem"
        +]
      • changedInput schema / properties / boards / items / properties / slug / description
        Previous 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}, or the Personio account in {account}.jobs.personio.de (letters, digits, hyphens; a dot is rejected). 1-100 chars. Example: \"ramp\" or \"acme\""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}, the Personio account in {account}.jobs.personio.de, the Pinpoint subdomain in {subdomain}.pinpointhq.com, or the Gem vanity in jobs.gem.com/{vanity}. Named feeds reject a dot. 1-100 chars. Example: \"ramp\" or \"acme\""
      • changedInput schema / properties / locations / description
        Previous value: -"Optional, 0-50 strings, any-of. Location substring match. Example: [\"london\"]"New value: +"Optional, 0-50 strings, any-of. Location substring match. US state names and codes match both ways; NYC, SF, LA, DC and Tampa Bay metro aliases match. Example: [\"london\"]"
  4. 6 tool updates
    • First observedjobs_check_open
    • First observedjobs_find_board
    • First observedjobs_get_board
    • First observedjobs_get_job
    • First observedjobs_list_seed_boards
    • First observedjobs_search_companies

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Live open jobs from companies' own Greenhouse, Lever and Ashby boards via their official public APIs, with publish-verified dates, age, long-open and repost signals, filters and change tracking.
    5
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Gives AI agents a daily-refreshed index of 350k+ open jobs scraped from 12,000+ company career sites on Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee and Personio. Enables keyword, country, remote, seniority and recency job searches, discovery of roles opened since yesterday, and weekly company hiring-signal rankings, with per-call cost caps.
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Live 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.
    31
    71 npm
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables asking what companies are hiring right now by querying live job postings from their applicant-tracking system board APIs (Greenhouse, Lever, Ashby, Recruitee, Rippling, Personio), with filters, parsed location, remote flag, and structured salary.
    3
    MIT
Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources