Fairquery Agent Trust & Data Safety
Server Details
PII/secret redaction, email checks, sanctions screening, OSV lookups and an MCP server pre-check.
- Status
- Healthy
- Last Tested
- Transport
- Streamable HTTP · MCP 2025-11-25
- URL
TDQS
Scored across 20 tools
Most tools have clearly distinct resource/action scopes, and descriptions explicitly disambiguate free vs paid and single-item vs batch workflows. Some boundaries remain close, especially email_check_syntax vs email_validate_deliverability, pii_scan_text vs pii_redact_text vs pii_guard_documents, and trust_screen_lead overlapping its component checks, but the WHEN/WHEN NOT notes make selection manageable.
All tools use snake_case with a domain prefix plus a clear action or object (email_*, jobs_*, osv_*, pii_*, sanctions_*, trust_*). mcp_server_precheck is slightly less verb-like, but the overall pattern is highly predictable.
Twenty tools is heavy and spans several unrelated domains: email validation, job boards, PII, OSV vulnerability checks, MCP prechecks, and sanctions screening. The breadth is useful, but the set feels more like a multi-purpose trust toolkit than a tightly scoped server, and the jobs tooling is tangential to the stated name.
Within each subdomain the surface is fairly complete: email has free prefilter plus paid deliverability, PII has scan/redact/restore/guard/list, OSV has query/get/audit, sanctions has sources/screen/merged trust flow, and jobs has discovery/listing/detail/check/search. Known gaps are explicitly called out rather than hidden, including no SMTP probing, no image OCR, no PEP/adverse media, and limited ATS coverage.
Available Tools
20 toolsemail_check_syntaxCheck email syntax, typos, disposable & role (free)ARead-onlyIdempotentInspect
WHAT: Offline email checks: RFC-style syntax + normalization, common domain typo "did you mean" (gmial.com -> gmail.com), disposable/temporary domain (bundled CC0 list, ~thousands of domains), role account (info@, sales@), free webmail provider. WHEN: Fast pre-filtering of signups or lead lists, before paying for email_validate_deliverability. WHEN NOT: Does NOT check DNS/MX, so it cannot tell whether the domain receives mail; use email_validate_deliverability (paid) for MX/SPF/DMARC. COST: Free; no token. RETURNS: Paged "email_check" rows; verdict is invalid | risky | unknown, never valid (dns_checked is always false). 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, results, or an Apify token. No further network call (no DNS/SMTP); addresses are not forwarded. Synchronous and CPU-bound, no rate limits: 10,000 addresses (the per-call maximum) return in well under a second. PARAMETERS: emails: each element is parsed on its own (one address per element; a comma-separated list in one element is INVALID_SYNTAX, not split). Before checking, "Name " is reduced to addr, a leading "mailto:" is dropped, the domain is lowercased (local part keeps its case), a trailing dot is removed and IDN domains are converted to punycode; the normalized value is returned as email, the original as input. Duplicates are not merged: every element yields one row, in input order. Role detection ignores +tags (sales+eu@ counts as sales@) and disposable matching also covers subdomains of listed domains. limit/offset page those rows; meta.summary always counts verdicts over ALL rows, not just the current page. EXAMPLE: {"emails":["someone@gmial.com"]} -> verdict "risky", reasons ["POSSIBLE_TYPO","DNS_NOT_CHECKED"], did_you_mean "someone@gmail.com".
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Optional page size: items to return, integer 1-1000 (default 100). Example: 50 | |
| emails | Yes | Required. 1-10000 strings, 1-320 chars each, one address per element; bare "jane@acme.com", "Jane <jane@acme.com>" and "mailto:jane@acme.com" forms accepted. Example: ["jane@acme.com","bob@gmial.com"] | |
| offset | No | Optional zero-based number of items to skip, integer 0-1000000 (default 0); pass nextOffset from the previous response, e.g. 100 |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context (summary counts, cost notes, ...) |
| count | Yes | Number of items in this page |
| items | Yes | Result rows for this page |
| total | Yes | Total items 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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds substantial behavioral context beyond annotations: it runs on a hosted Worker, inputs leave the client, it is designed not to store inputs/results/tokens, makes no DNS/SMTP network calls, is synchronous and CPU-bound, and has no rate limits with 10,000 addresses returning quickly. It also discloses that verdicts never include valid because DNS is not checked. These details are consistent with readOnlyHint, idempotentHint, destructiveHint=false, and openWorldHint=false.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but front-loaded and organized under clear labels (WHAT, WHEN, WHEN NOT, COST, RETURNS, BEHAVIOR, PARAMETERS, EXAMPLE). Each section carries operational value for a tool with several edge cases, so the length is justified rather than redundant.
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 tool's complexity, the schema, annotations, and output schema, the description is complete enough for an agent to call it correctly. It covers purpose, exclusions, normalization edge cases, paging behavior, cost, execution environment, and expected return semantics.
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?
Although schema coverage is already 100%, the description adds important semantics: each array element is parsed independently, comma-separated lists in one element are invalid, normalization rules are specified, duplicates are not merged, rows preserve input order, and limit/offset page rows while meta.summary always counts all rows. This meaningfully extends 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?
The description states specific offline email checks: syntax/normalization, typo suggestions, disposable domains, role accounts, and free webmail. It explicitly distinguishes this pre-filter from the paid delivery-validation sibling, so an agent can route correctly without opening either 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?
It provides explicit WHEN and WHEN NOT guidance, naming email_validate_deliverability as the alternative for DNS/MX/SPF/DMARC checks. The use case is framed clearly as fast pre-filtering of signups or lead lists before paying for delivery validation.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
email_validate_deliverabilityValidate email deliverability: MX, SPF, DMARC (paid)ARead-onlyIdempotentInspect
WHAT: Runs the Fairquery Apify Actor email-validator-mx-disposable: syntax, domain existence, MX/null-MX, disposable, role, free provider, typo suggestion, SPF/DMARC policy, mail provider; final verdict valid | risky | invalid | unknown. WHEN: Only when MX/DNS matters (can the domain receive mail?), e.g. cleaning lists before a send or verifying a lead's domain; pre-filter with email_check_syntax to avoid paying for obviously bad rows. WHEN NOT: Offline format, typo, disposable or role checks (free email_check_syntax). Leads that also need sanctions screening: trust_screen_lead (one merged decision). It does not SMTP-probe mailboxes. COST: Paid, billed to YOUR Apify account via your APIFY_TOKEN: $0.79 per 1,000 emails (+ $0.05 per 1,000 runs). 100 emails ~ $0.08. RETURNS: Paged "email" rows (verdict valid | risky | invalid | unknown with reasons[]) or "error" rows. Tool errors: APIFY_TOKEN_MISSING, APIFY_TOKEN_INVALID, APIFY_PAYMENT_REQUIRED, APIFY_ACTOR_RUN_FAILED, APIFY_TIMEOUT. BEHAVIOR: No email is sent; results change only with live DNS. Called from this Fairquery-hosted Worker, which forwards your input and Apify token to Apify and waits 20 s (APIFY_TIMEOUT if the run has not finished). This Worker's code is designed not to store them. The Actor in your account does the DNS/MX lookups. PARAMETERS: emails: elements may also hold comma/semicolon/newline-separated lists or "Name " forms; the Actor splits them, trims, and skips case-insensitive duplicates, so you pay once per unique address. The allow* flags only decide whether a condition downgrades the verdict to "risky": the matching reason code (ROLE_ACCOUNT, DISPOSABLE_DOMAIN, FREE_PROVIDER) is reported in reasons[] either way, and they can never turn an "invalid" address (bad syntax, no domain, null MX, no mail server) into "valid". POSSIBLE_TYPO and IMPLICIT_MX_ONLY always make it "risky" regardless of flags. Typical presets: B2B lead list = allowFreeProviders false + allowRoleAccounts false; consumer signups = defaults. checkSpfDmarc only adds has_spf / has_dmarc / dmarc_policy fields; it never changes the verdict or the price. limit/offset only page the returned rows: every call validates and bills the whole list again (also when only offset changes), so set limit (max 1000) to cover all rows instead of paging. EXAMPLE: {"emails":["john@stripe.com"]} -> verdict "valid", has_mx true, mail_provider "Google Workspace / Gmail".
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Optional page size: items to return, integer 1-1000 (default 100). Example: 50 | |
| emails | Yes | Required. 1-10000 strings, 1-320 chars each, one address per element; bare "jane@acme.com", "Jane <jane@acme.com>" and "mailto:jane@acme.com" forms accepted. Example: ["jane@acme.com","bob@gmial.com"] | |
| offset | No | Optional zero-based number of items to skip, integer 0-1000000 (default 0); pass nextOffset from the previous response, e.g. 100 | |
| checkSpfDmarc | No | Optional boolean, default true. true adds has_spf, has_dmarc and dmarc_policy (none|quarantine|reject) per domain; false skips those DNS lookups. Verdict and price are unaffected | |
| allowDisposable | No | Optional boolean, default false = disposable/temporary-inbox domains (mailinator.com, ...) get verdict "risky"; true stops penalising them | |
| allowRoleAccounts | No | Optional boolean, default true. false downgrades role mailboxes (info@, sales@, support@, ...) to verdict "risky" | |
| allowFreeProviders | No | Optional boolean, default true. false downgrades free webmail (gmail.com, outlook.com, yahoo.com, ...) to "risky"; recommended for B2B lead lists |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context (summary counts, cost notes, ...) |
| count | Yes | Number of items in this page |
| items | Yes | Result rows for this page |
| total | Yes | Total items 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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare read-only/open-world/idempotent, but the description adds the traits that actually change agent behavior: paid with exact per-1,000 pricing billed to the caller's Apify account, a 20-second wait, an enumerated set of APIFY_* error codes, and the fact that no SMTP probe or email send occurs. It also discloses the Worker-to-Apify forwarding path and that results vary only with live DNS.
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 with a WHAT/WHEN/WHEN NOT/COST/RETURNS/BEHAVIOR/PARAMETERS/EXAMPLE skeleton, so an agent can stop reading at any level of detail. Length is justified by 7 parameters, live billing, and timeout semantics; the only mild redundancy is the pricing restated as an arithmetic example, which still aids cost estimation.
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?
Covers everything needed to invoke correctly: cost model, timeout ceiling, error taxonomy, output row shapes, and the billing trap around paging. Even though an output schema exists, the description of "email" vs "error" rows and the reasons[] field is a useful complement rather than padding.
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% (baseline 3), but the description adds semantics the schema cannot express: input splitting/dedup so you pay once per unique address, the rule that allow* flags can only downgrade to "risky" and never upgrade an invalid address, that POSSIBLE_TYPO and IMPLICIT_MX_ONLY are unconditional, and that offset re-bills the whole list. These are genuinely decision-changing details absent from the property descriptions.
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+resource (deliverability validation via a named Actor) and enumerates the exact checks performed and the four possible verdicts. It explicitly separates itself from email_check_syntax (offline checks) and trust_screen_lead (merged decision), so an agent can route without opening either 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?
Has explicit WHEN and WHEN NOT sections naming the alternative tools and the deciding condition ("only when MX/DNS matters", "pre-filter with email_check_syntax"). It also gives concrete preset configurations (B2B list vs consumer signups), which removes most remaining inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
jobs_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 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 has no per-job endpoint, so the whole board is fetched once and searched for the id (method "board_listing"). Personio has no per-job endpoint either: robots.txt, then the documented /xml feed only, and the position id is looked up (method "board_listing"). 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 or Personio id costs one full board download (Personio is robots.txt plus /xml); on Greenhouse and Lever a 404 triggers one extra board request so a closed job (status closed) is told apart from a missing board (BOARD_NOT_FOUND). includeDescription adds no request on Greenhouse and Lever (the per-job response already holds the text) and makes the Ashby or Personio board download include descriptions; 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"}.
| Name | Required | Description | Default |
|---|---|---|---|
| ats | No | Required unless jobKey is given. The applicant-tracking system hosting the board: "greenhouse" | "lever" | "ashby" | "personio" (the first part of job_key). Example: "greenhouse" | |
| slug | No | Required unless jobKey is given. Board slug from the public board URL (boards.greenhouse.io/{slug}, jobs.lever.co/{slug}, jobs.ashbyhq.com/{slug}, 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" | |
| jobId | No | Required unless jobKey is given. The ATS job id (job_id field, the last part of job_key), 1-100 chars: numeric on Greenhouse and Personio, a UUID on Lever and Ashby. Example: "8172503" | |
| jobKey | No | Optional if ats + slug + jobId are given; wins when both are given. The job_key field of a job row, format "{ats}:{slug}:{job_id}" with ats in lowercase (greenhouse | lever | ashby | personio); any other shape returns INVALID_INPUT. Examples: "greenhouse:stripe:8172503", "personio:acme:4103" | |
| includeDescription | No | Optional boolean, default false. true adds job: the full job row (title, location, compensation, posted_at, description_text, ...) when the job is open. Example: true |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context: board totals, request counts, cost notes |
| count | Yes | Number of items in this page |
| items | Yes | Result rows. Each has "type": "job" (job_key, title, company, locations, countries, remote, posted_at, verified_open_at, age_days, long_open, apply_url, source_url, ...), "board", "company", "seed_board", "open_check" or "error" (code, message, retryable) |
| total | Yes | Total items after filters, before paging |
| offset | Yes | Offset of the first item in this page |
| hasMore | Yes | True if more items exist after this page |
| nextOffset | Yes | Offset to pass for the next page, or null when done |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations, the description discloses that execution occurs on a Fairquery-hosted Cloudflare Worker, inputs leave the client, and the Worker is designed not to store inputs or results. It also explains ATS-specific request behavior, 404 handling, Ashby/Personio board-listing fallbacks, retryable errors, and the precise meaning of 'open'.
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 description is long but strongly structured with WHAT, WHEN, WHEN NOT, BEHAVIOR, COST, RETURNS, PARAMETERS, and EXAMPLE sections. Most content earns its place for a complex, multi-ATS tool, though some ATS-specific details are repeated and make it denser than strictly necessary.
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?
The definition covers purpose, alternatives, behavioral constraints, costs, return shape, error modes, and parameter implications. With annotations and an output schema also present, an agent has enough information to select and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already defines each parameter thoroughly. The description still adds meaningful operational semantics: malformed jobKey or incomplete ats+slug+jobId returns INVALID_INPUT without a request, includeDescription affects Ashby/Personio board downloads, and description_text has specific processing limits.
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 description states a specific verb and resource: re-check ONE job on the employer's ATS now and report open or closed. It also distinguishes this tool from sibling tools such as jobs_get_job, 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?
The WHEN and WHEN NOT sections are explicit, naming the exact alternatives for full detail, board listing, and bulk checking. The conditions for selecting this tool versus its siblings are unambiguous.
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. A Personio URL or personio:account 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 career URL. 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, Rippling or LinkedIn pages, and it does not discover Personio accounts. 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} and https://{account}.jobs.personio.de/..., 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 hosts are not crawled) -> at most 6 GETs against the Greenhouse, Lever and Ashby APIs with slug guesses. Personio is never probed and a careers-page link to Personio 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 account URL or personio:account) 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) and the first hit wins, so an ambiguous name returns at most one medium-confidence board. A starter-list match can return several boards (all high). EXAMPLE: {"company":"ramp.com"} -> [{"ats":"ashby","slug":"ramp","via":"seed_list","confidence":"high"}].
| Name | Required | Description | Default |
|---|---|---|---|
| company | Yes | Required. Company name, domain or URL, 2-300 chars. Examples: "Ramp", "sentry.io", "https://jobs.lever.co/palantir" |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context: board totals, request counts, cost notes |
| count | Yes | Number of items in this page |
| items | Yes | Result rows. Each has "type": "job" (job_key, title, company, locations, countries, remote, posted_at, verified_open_at, age_days, long_open, apply_url, source_url, ...), "board", "company", "seed_board", "open_check" or "error" (code, message, retryable) |
| total | Yes | Total items after filters, before paging |
| offset | Yes | Offset of the first item in this page |
| hasMore | Yes | True if more items exist after this page |
| nextOffset | Yes | Offset to pass for the next page, or null when done |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only, open-world and idempotent, yet the description adds substantial undisclosed behavior: inputs leave the client, the lookup pipeline order, robots.txt and private-IP refusal, at most 6 ATS GETs, retry/backoff, and a 403/429 kill switch that degrades to a HOST_BLOCKED meta note rather than an error. The only real gap is that the rate-limit and backoff numbers are more operational detail than decision-relevant, diluting rather than informing.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The WHAT/WHEN/WHEN NOT/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE headings make it scannable and front-loaded, and the routing information comes first. But for a one-parameter tool it is roughly 400 words, with low-value specifics (0.5–1.6 s backoff, 15 s timeout, exact probe count) that an agent cannot act on.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained, yet the description still clarifies the row shape, the NO_BOARD_FOUND error row, and the confidence semantics of slug_probe hits. Nothing an agent needs in order to call this correctly and interpret the result 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% and the schema already gives the three example forms, but the description goes well beyond it: board URL and personio:account are parsed with no network call, a domain enables the careers-page step, a plain name only uses the starter list and slug probes. It also documents suffix stripping and the Greenhouse→Lever→Ashby probe order, so the agent can predict which form to pass for the result it wants.
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/URL to its public job board on Greenhouse, Lever or Ashby. It immediately delimits scope against siblings by naming jobs_get_board and jobs_search_companies and by listing unsupported ATSes (Workday, Workable, Recruitee, Rippling, LinkedIn).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The WHEN / WHEN NOT block is explicit: use it before jobs_get_board when you only know the company name or have a Personio URL; skip it when you already have ats + slug or want companies plus jobs in one step. It also states a negative capability set so the agent does not waste a call.
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) or from Personio's documented XML feed for an account you name, 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 you already know the company subdomain. WHEN NOT: One known job's full detail (jobs_get_job). 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 accounts 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 (no other path, query or apply URL); all Personio accounts share one lane, one request at a time, at least 1.5 s apart. 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 account does not block other accounts. Lever requests are spaced 1 s apart (its robots.txt Crawl-delay). COST: No APIFY_TOKEN, no billing. RETURNS: Paged "job" rows. compensation only when the ATS publishes it; posted_at is the ATS date or null (never guessed); age_basis says how age_days was derived. PARAMETERS: All filters are AND-ed; values inside one filter are OR-ed. titleIncludes/titleExcludes match whole words ("engineer" does not match "Engineering"). countries uses the ATS's structured country where present (Lever, Ashby) and a place-name list otherwise; rows without any country are dropped when countries is set. postedWithinDays drops rows with posted_at = null. Paging re-fetches the board, so pages from different calls can shift if the employer edits the board in between. EXAMPLE: {"ats":"ashby","slug":"ramp","titleIncludes":["engineer"],"remote":"only"} -> remote engineering roles at Ramp with salary ranges.
| Name | Required | Description | Default |
|---|---|---|---|
| ats | Yes | Required. The applicant-tracking system hosting the board: "greenhouse" | "lever" | "ashby" | "personio". Personio is the company account you name, not a discovered board. 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}, or the Personio account in {account}.jobs.personio.de (letters, digits, hyphens; a dot is rejected). 1-100 chars. Example: "ramp" or "acme" | |
| 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?
Beyond the annotations, it discloses hosted Worker execution, input egress, no-storage design, per-host request behavior, Personio lane throttling, Lever Crawl-delay spacing, 403/429 kill-switch behavior, no-history semantics, and cost. These details materially help an agent understand safety, latency, and failure modes.
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 description is dense but well-structured with WHAT, WHEN, WHEN NOT, BEHAVIOR, COST, RETURNS, PARAMETERS, and EXAMPLE sections. For a 12-parameter tool with multiple ATS backends and operational caveats, the length is justified and front-loaded.
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?
It covers purpose, usage routing, supported and unsupported providers, behavioral constraints, request patterns, cost, return semantics, filter semantics, and an example. With annotations and an output schema already present, the description supplies everything an agent needs to select and call the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, but the description still adds meaningful semantics beyond the schema: filters are AND-ed while values within one filter are OR-ed, title matching uses whole words, country filtering can drop unknown-country rows, and postedWithinDays drops null posted_at rows. It also explains paging drift because pages re-fetch the board.
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 description states a specific verb and resource: fetch every currently published job from one named board via an official ATS API or Personio feed, normalize, and filter it. It explicitly covers supported ATS types and distinguishes itself from siblings such as jobs_get_job, 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It provides explicit WHEN and WHEN NOT guidance, including the prerequisite that the ats and slug are already known. It names alternatives for single-job detail, multi-company/history search, unknown boards, and Personio discovery, and lists unsupported ATS providers.
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)ARead-onlyIdempotentInspect
WHAT: Fetch the full detail of ONE job that is currently published on its official public ATS API (Greenhouse, Lever or Ashby) or on a Personio XML feed for an account you name, normalized the same way as a board listing, including its description. 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. WHEN NOT: Open or closed only (jobs_check_open). Every opening on a board (jobs_get_board). 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 has no per-job endpoint, so the board is fetched once and the id is looked up. Personio has no per-job endpoint: robots.txt, then GET https://{account}.jobs.personio.de/xml only, and the position id is looked up. No apply URL is requested. HTTP 404/410 on Greenhouse or Lever, when the board itself exists, or an id missing from an Ashby or Personio 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. 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 id downloads the whole board with descriptions (one request, the same board GET as jobs_get_board with descriptions). A Personio id downloads the documented /xml feed (robots.txt plus /xml) and looks up the position id. 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). description_text is always included when the job is open: plain text, at most 20,000 characters, e-mail addresses and phone numbers removed. EXAMPLE: {"jobKey":"greenhouse:stripe:8172503"} -> that Stripe job's row.
| Name | Required | Description | Default |
|---|---|---|---|
| ats | No | Required unless jobKey is given. The applicant-tracking system hosting the board: "greenhouse" | "lever" | "ashby" | "personio" (the first part of job_key). Example: "greenhouse" | |
| slug | No | Required unless jobKey is given. Board slug from the public board URL (boards.greenhouse.io/{slug}, jobs.lever.co/{slug}, jobs.ashbyhq.com/{slug}, 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" | |
| jobId | No | Required unless jobKey is given. The ATS job id (job_id field, the last part of job_key), 1-100 chars: numeric on Greenhouse and Personio, a UUID on Lever and Ashby. Example: "8172503" | |
| jobKey | No | Optional if ats + slug + jobId are given; wins when both are given. The job_key field of a job row, format "{ats}:{slug}:{job_id}" with ats in lowercase (greenhouse | lever | ashby | personio); any other shape returns INVALID_INPUT. Examples: "greenhouse:stripe:8172503", "personio:acme:4103" |
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?
Beyond annotations, it discloses the hosted-worker execution model, that inputs leave the client, no storage design, which hosts receive which identifiers, rate-limit behavior, kill-switch behavior on 403/429, and precise error mappings. This is unusually strong behavioral disclosure for an open-world read-only tool.
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 description is long but tightly structured with WHAT, WHEN, WHEN NOT, BEHAVIOR, COST, RETURNS, PARAMETERS, and EXAMPLE. It is front-loaded with the key distinction and every section carries operational information relevant to correct invocation.
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 open-world complexity, multiple supported ATS providers, output schema, and annotations, the description is complete: it covers return shape, error taxonomy, retryability, sanitization, and provider-specific request behavior. An agent should be able to call this correctly without extra documentation.
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?
Although schema coverage is 100%, the description adds substantive semantics: jobKey malformed or incomplete ats + slug + jobId returns INVALID_INPUT with no request, Ashby and Personio ids trigger board/feed downloads rather than per-job endpoints, and descriptions are capped and sanitized. These details go beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: fetching the full detail of ONE currently published job from supported ATS sources, including its description. It clearly distinguishes this from listing all openings or checking open/closed status, so an agent can select it 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit WHEN and WHEN NOT sections, naming jobs_check_open, jobs_get_board, and jobs_search_companies as alternatives with the conditions that select them. It also lists unsupported ATS providers to prevent misselection.
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: 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 (grouped by ATS, so offsets are stable). PARAMETERS: ats narrows the list; omit it for the whole starter list (Greenhouse, Lever, Ashby). Personio is 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.
| Name | Required | Description | Default |
|---|---|---|---|
| ats | No | Optional filter: "greenhouse" | "lever" | "ashby" | "personio"; default all. The starter list has no Personio boards | |
| limit | No | Optional page size, integer 1-500, default 100 | |
| offset | No | Optional items to skip, integer >= 0, default 0; pass nextOffset from the previous response |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context: board totals, request counts, cost notes |
| count | Yes | Number of items in this page |
| items | Yes | Result rows. Each has "type": "job" (job_key, title, company, locations, countries, remote, posted_at, verified_open_at, age_days, long_open, apply_url, source_url, ...), "board", "company", "seed_board", "open_check" or "error" (code, message, retryable) |
| total | Yes | Total items after filters, before paging |
| offset | Yes | Offset of the first item in this page |
| hasMore | Yes | True if more items exist after this page |
| nextOffset | Yes | Offset to pass for the next page, or null when done |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover readOnly, idempotent, and openWorld=false (offline), so the safety profile is largely pre-declared. The description still adds real value beyond that: hosted Worker execution, inputs leaving the client, no storage of inputs or results, no further network call, no APIFY_TOKEN/billing, and staleness of jobs_at_verification. It stops short of describing the response envelope or pagination mechanics in prose, which keeps it at a solid but not exceptional level for an annotated tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Labeled sections (WHAT/WHEN/WHEN NOT/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE) make it front-loaded and skimmable, and every block targets a real decision. It is longer than necessary for a 3-parameter read tool, with the Personio exclusion stated twice and a COST section that overlaps the offline claim.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no prose explanation, and the description still flags the stale snapshot field and the paged ordering. Combined with annotations and full schema coverage, an agent has everything needed to call this correctly and chain into jobs_get_board with ats + slug.
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; the description exceeds it by explaining that ats narrows the list, that omitting it yields all three ATSs, and critically that meta.by_ats ignores ats so paging totals must come from total. That by_ats caveat is non-obvious semantics the schema cannot express. The Personio exclusion is repeated from the schema, costing a little.
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 gives a specific verb (list) and resource (bundled starter list of company job boards) plus the exact fields returned. It explicitly distances itself from jobs_get_board (live data) and jobs_find_board (off-list companies), so an agent can pick it without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Dedicated WHEN and WHEN NOT blocks name the two sibling alternatives and the exact condition that selects each. It also scopes expectations ('a starting point, not a global index of employers'), which prevents misuse as a full employer index.
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 for Greenhouse, Lever and Ashby, a Personio XML feed when you name the account, 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), 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, 403/429 kill switch, robots.txt on careers pages. Personio accounts are not discovered. 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}.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Optional page size, integer 1-500, default 50 | |
| boards | No | Optional. 0-500 exact boards. Example: [{"ats":"lever","slug":"palantir"}] | |
| offset | No | Optional items to skip, integer >= 0, default 0; pass nextOffset from the previous response | |
| remote | No | Optional, "any" | "only" | "exclude", default "any" (no remote filter). "only" keeps job rows with remote = true (an ATS remote flag, or remote wording such as "Remote" or "Anywhere" in the location or title; see remote_basis); "exclude" drops those rows and keeps rows where remote is false or unknown (null) | |
| maxJobs | No | Optional integer 1-50000, default 5000: spend cap on billed job rows | |
| onlyNew | No | Optional boolean, default false: only new/changed jobs since the last tracked run (plus free closed rows) | |
| companies | No | Optional (see PARAMETERS). 0-200 names, domains or board URLs. Example: ["stripe.com","Ramp","https://jobs.lever.co/palantir"] | |
| countries | No | Optional ISO-2 codes, any-of. Example: ["US","GB"] | |
| locations | No | Optional, 0-50 strings, any-of. Location substring match. Example: ["london"] | |
| confirmOpen | No | Optional boolean, default false: re-check each emitted job on its detail endpoint ($0.29 per 1,000 re-checks, max 200 per run) | |
| departments | No | Optional, 0-50 strings, any-of. Department/team substring match. Example: ["engineering"] | |
| useSeedList | No | Optional boolean, default false: also scan all verified starter boards (each billed as a board) | |
| excludeStale | No | Optional boolean, default false. true drops job rows with long_open = true (age_days > 60, counted from posted_at, or from first_seen_at when the ATS gives no date) or reposted = true (a new job id matching a job that closed on the same board within 90 days, from the history in stateStoreName, so never on a board's first tracked run). Free closed rows are not filtered by it | |
| titleExcludes | No | Optional, 0-50 strings, any-of. Whole-word title exclusion. Example: ["intern"] | |
| titleIncludes | No | Optional, 0-50 strings, any-of. Whole-word title match. Example: ["engineer"] | |
| stateStoreName | No | Optional, default "fairquery-jobs-feed-state": named key-value store for history in your Apify account | |
| postedWithinDays | No | Optional integer 1-3650, default off: keep only job rows whose ATS publish date (posted_at) is at most this many whole days old; rows without an ATS publish date (posted_at = null) are dropped. Free closed rows are not filtered by it. Example: 7 | |
| includeDescription | No | Optional boolean, default false: add plain-text descriptions (PII-scrubbed) |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context: board totals, request counts, cost notes |
| count | Yes | Number of items in this page |
| items | Yes | Result rows. Each has "type": "job" (job_key, title, company, locations, countries, remote, posted_at, verified_open_at, age_days, long_open, apply_url, source_url, ...), "board", "company", "seed_board", "open_check" or "error" (code, message, retryable) |
| total | Yes | Total items after filters, before paging |
| offset | Yes | Offset of the first item in this page |
| hasMore | Yes | True if more items exist after this page |
| nextOffset | Yes | Offset to pass for the next page, or null when done |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial behavior beyond annotations: APIFY_TOKEN requirement, Worker forwarding and 20-second wait with APIFY_TIMEOUT, actor execution in the caller's Apify account, history stored in a named key-value store, politeness rules, and output being that run's rows rather than a public directory. It also discloses billing structure and that every call starts a new billed run, which is important for a non-idempotent paid operation.
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?
Despite being long, it is tightly structured with WHAT, WHEN, WHEN NOT, BEHAVIOR, COST, RETURNS, PARAMETERS, and EXAMPLE headings. The most decision-relevant information is front-loaded, and the length is justified by the 18-parameter paid tool's complexity.
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?
Complete for a complex paid, non-idempotent, open-world tool: it covers authentication, timeout behavior, state storage, pricing examples, legal/robots constraints, return row types, pagination semantics, and a concrete example. No material gap remains for an agent to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Even with 100% schema coverage, the description adds critical cross-parameter semantics the schema cannot express: at least one of companies, boards, or useSeedList is required despite all being schema-optional, onlyNew compares against the last run for the same stateStoreName, and limit/offset page the current result on the Worker rather than triggering new runs. These details materially improve correct invocation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: run the Fairquery Apify Actor over many companies or boards, including discovery, filters, history, and re-checks. It explicitly distinguishes itself from sibling tools in WHEN NOT (jobs_get_board, jobs_get_job, jobs_check_open), so an agent can route correctly without opening schemas.
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?
Gives explicit WHEN and WHEN NOT conditions, naming the exact alternatives for one board, one job detail, and one re-check. It also clarifies the monitoring use case and the scale threshold where this tool is appropriate.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
mcp_server_precheckPre-check an MCP server (free scan on this Worker)ARead-onlyIdempotentInspect
WHAT: Pre-install check for one MCP server. Scans tool names, descriptions and parameter text you pass for injection phrasing, concealed characters, hidden markup and cross-server shadowing. WHEN: Before installing or enabling an MCP server, once you have a tools/list result to pass as tools or toolsListJson. WHEN NOT: For a dependency audit with no tool list, call osv_audit_dependencies. For a few exact versions, call osv_query_packages. This tool does not install, start or execute the package. COST: The scan is free and runs on this Fairquery-hosted Worker (Cloudflare), not on the caller's machine; tool names and descriptions you send leave the client. This Worker's code is designed not to store them and does not forward them to Apify. path and packageName are refused (PATH_NOT_ALLOWED) because this Worker has no disk, so auditDependencies=true cannot read a manifest and does not start the OSV Actor. To audit a manifest, call osv_audit_dependencies (paid: $0.50 per 1,000 packages + $1.50 per 1,000 vulnerabilities found). RETURNS: Paged "finding" rows (rule, severity, tool, field, evidence). If auditDependencies is true, one "dependency_audit" row with status no_manifest on this Worker (no disk). meta.risk_level is none, low, medium, high, critical or not_assessed. meta.shared_externally.tool_names_and_descriptions is always false, which means the scan does not forward that text to npm or Apify; the text is still received by this Worker. Errors: INPUT_EMPTY, INVALID_JSON, INVALID_PACKAGE_NAME, PATH_NOT_ALLOWED, INPUT_TOO_LARGE. BEHAVIOR: Default auditDependencies=false: no further network call and no process spawn. Scans only tools or toolsListJson you pass. path and packageName return PATH_NOT_ALLOWED and nothing is read. This Worker has no disk, so it does not read package.json, lockfiles, manifest.json, or server.json, and it does not follow symlinks. auditDependencies=true does not call registry.npmjs.org or the OSV Actor from this Worker, because a manifest is never loaded. PARAMETERS: Pass tools or toolsListJson (neither, and no path or packageName -> INPUT_EMPTY). tools: structured tools/list entries. toolsListJson: raw JSON of a tools/list result, an MCPB manifest or a tools array; invalid JSON -> INVALID_JSON. path and packageName always fail on this Worker (no disk): an invalid packageName is INVALID_PACKAGE_NAME, and any other path or packageName is PATH_NOT_ALLOWED. Nothing is read or downloaded. Use the npm server @fairquery/agent-trust-mcp for a path or packageName scan. Each field is scanned up to 20,000 characters and the raw total must be <= 500,000. auditDependencies defaults to false and does not start a paid run here. minSeverity and includeDevDependencies are unused on this Worker. limit/offset page the combined rows. EXAMPLE: {"tools":[{"name":"sum","description":"Add two numbers."}]} -> risk_level "none", shared_externally.tool_names_and_descriptions false. A poisoned description returns finding rows and a higher risk_level.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | Refused on this hosted Worker: any path returns PATH_NOT_ALLOWED (no disk). Pass tools or toolsListJson. Example: "/tmp/some-mcp" | |
| limit | No | Optional page size: items to return, integer 1-1000 (default 100). Example: 50 | |
| tools | No | Optional if toolsListJson, path or packageName is given. 0-400 tools, as returned by tools/list. Example: [{"name":"sum","description":"Add two numbers."}] | |
| offset | No | Optional zero-based number of items to skip, integer 0-1000000 (default 0); pass nextOffset from the previous response, e.g. 100 | |
| minSeverity | No | Optional, one of "ALL" | "LOW" | "MEDIUM" | "HIGH" | "CRITICAL", default "ALL". Forwarded to the OSV Actor only when auditDependencies is true | ALL |
| packageName | No | Refused on this hosted Worker. An invalid npm name returns INVALID_PACKAGE_NAME; any other packageName returns PATH_NOT_ALLOWED (no disk, and the package is not downloaded). Pass tools or toolsListJson. Example: "some-mcp" | |
| toolsListJson | No | Optional if tools, path or packageName is given. Raw JSON (<= 2,000,000 chars) of a tools/list result, {"tools":[...]}, an MCPB manifest, or a tools array. Example: "{\"tools\":[{\"name\":\"sum\",\"description\":\"Add two numbers.\"}]}" | |
| auditDependencies | No | Optional boolean, default false. On this Worker a manifest is not read, so true does not start the OSV Actor; the audit row is no_manifest. Use osv_audit_dependencies for a pasted manifest. Example: false | |
| includeDevDependencies | No | Optional boolean, default true. Forwarded to the OSV Actor only when auditDependencies is true; false skips devDependencies |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context (summary counts, cost notes, ...) |
| count | Yes | Number of items in this page |
| items | Yes | Result rows for this page |
| total | Yes | Total items 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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Far beyond the annotations: it discloses that the scan is free, runs on a hosted Cloudflare Worker rather than the caller's machine, that tool names and descriptions leave the client, that the Worker is designed not to store or forward them to Apify, and that durable scans elsewhere are paid ($0.50/1,000 packages). It also discloses the no-disk constraint that makes path/packageName fail and that no network call or process spawn occurs at defaults — consistent with openWorldHint=true.
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?
Strongly front-loaded with WHAT/WHEN/WHEN NOT and scannable section labels, and each section earns its place. It loses a point for repetition — the 'path and packageName refused, no disk' point is restated in COST, BEHAVIOR and PARAMETERS, and the paid-pricing detail appears twice.
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 9 optional parameters, a hostile-input domain and a real output shape, the description covers preconditions, cost, data handling, error codes, return-row structure, risk_level values and paging. An output schema exists, so leaving return-value detail light is appropriate and it still summarizes the finding rows and meta fields.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already documents per-parameter refusals, so the baseline is 3. The description adds genuine cross-parameter semantics not in the schema: tools/toolsListJson mutual exclusion yielding INPUT_EMPTY, the 20,000-char per-field and 500,000-char total scan caps, minSeverity and includeDevDependencies being inert on this Worker, and the example payload.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The WHAT line states a specific verb and resource ('pre-install check for one MCP server') and enumerates exactly what it scans: tool names, descriptions, parameter text, for injection phrasing, concealed characters, hidden markup and cross-server shadowing. It explicitly distinguishes itself from osv_audit_dependencies and osv_query_packages, so an agent can route without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
WHEN names the precondition (before installing/enabling, once you have a tools/list result) and WHEN NOT names two concrete alternatives with their selecting conditions (dependency audit with no tool list, a few exact versions). It also states a hard boundary: it does not install, start or execute the package.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
osv_audit_dependenciesAudit dependencies/manifests with fix recommendations (paid)ARead-onlyIdempotentInspect
WHAT: Runs the Fairquery Apify Actor osv-dependency-vulnerability-check: parses package.json, package-lock.json (v1-v3), requirements.txt or go.mod (or a package list), checks OSV, and returns per-package max severity/CVSS, fixed versions, a recommended upgrade version and one row per vulnerability (deduped aliases). WHEN: Only for manifests, severity filtering or fix recommendations: full project audits from a pasted manifest, CI-style reports; for a few exact versions use osv_query_packages (free). WHEN NOT: Quick checks of a few exact versions (osv_query_packages, free) or one advisory (osv_get_vulnerability, free). COST: Paid, billed to YOUR Apify account via your APIFY_TOKEN: $0.50 per 1,000 packages + $1.50 per 1,000 vulnerabilities found (+ $0.05 per 1,000 runs). Use minSeverity="HIGH" to cut vuln rows. lodash+django (2 pkgs, 41 vulns) ~ $0.06. RETURNS: Paged rows type="package" (vulnerable, vuln_count, max_severity, fixed_versions, recommended_version), type="vulnerability" (id, cve_ids, severity, cvss_score, fixed_versions, osv_url) or type="error". Tool errors: INPUT_EMPTY, APIFY_TOKEN_MISSING, APIFY_TOKEN_INVALID, APIFY_PAYMENT_REQUIRED, APIFY_ACTOR_RUN_FAILED, APIFY_TIMEOUT. BEHAVIOR: Called from this Fairquery-hosted Worker, which forwards your input and Apify token to Apify and waits 20 s (APIFY_TIMEOUT if the run has not finished). This Worker's code is designed not to store them. The Actor in your account queries OSV.dev. Up to 1000 packages or a 2,000,000-char manifest. PARAMETERS: packages or manifestText = input (one required); manifestType = format, default auto-detect; minSeverity = only report at/above ALL|LOW|MEDIUM|HIGH|CRITICAL (default ALL); includeDevDependencies (default true); limit = page size (default 100, max 1000); offset = items to skip for paging (default 0). EXAMPLE: {"manifestText":"django==3.2.0\nrequests==2.19.0","minSeverity":"HIGH"}
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Optional page size: items to return, integer 1-1000 (default 100). Example: 50 | |
| offset | No | Optional zero-based number of items to skip, integer 0-1000000 (default 0); pass nextOffset from the previous response, e.g. 100 | |
| packages | No | Optional if manifestText is given (at least one is required). 0-1000 {ecosystem, name, version} objects with exact versions; billed per package. Example: [{"ecosystem":"npm","name":"lodash","version":"4.17.20"}] | |
| minSeverity | No | Optional, one of "ALL" | "LOW" | "MEDIUM" | "HIGH" | "CRITICAL", default "ALL". Only emit (and bill) vulnerability rows at or above this severity; package rows are always emitted. Example: "HIGH" | ALL |
| manifestText | No | Optional if packages is given. Raw file content (<= 2,000,000 chars) of package.json, package-lock.json (v1-v3), requirements.txt (== pins) or go.mod; parsed packages are added to packages. Example: "django==3.2.0\nrequests==2.19.0" | |
| manifestType | No | Optional, one of "auto" | "package.json" | "package-lock.json" | "requirements.txt" | "go.mod", default "auto" (detect from content). Set it when detection fails (MANIFEST_UNDETECTED) | auto |
| includeDevDependencies | No | Optional boolean, default true. false skips devDependencies (package.json) and dev packages (package-lock.json); no effect on requirements.txt / go.mod |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context (summary counts, cost notes, ...) |
| count | Yes | Number of items in this page |
| items | Yes | Result rows for this page |
| total | Yes | Total items 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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only cover the safety profile (readOnly, idempotent, non-destructive); the description adds substantial context beyond that: per-package/per-vulnerability pricing billed to the caller's Apify account, a 20 s timeout with APIFY_TIMEOUT as the failure mode, a concrete cost example, an enumerated error-code list (APIFY_PAYMENT_REQUIRED, APIFY_TOKEN_MISSING, etc.), and input size ceilings. The privacy note about token forwarding is hedged ('designed not to store') but is volunteered rather than omitted.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Labeled sections (WHAT/WHEN/WHEN NOT/COST/RETURNS/BEHAVIOR/PARAMETERS/EXAMPLE) front-load purpose and routing before cost and mechanics. Dense but every block carries information an agent needs for a paid, third-party-hosted call; nothing is filler.
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 7-parameter, paid, open-world tool with an output schema, the definition covers invocation requirements (token), cost model, failure modes, size limits and paging. The RETURNS block duplicates the output schema but the duplication is harmless and the rest leaves no ambiguity about how to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 100%, so the description's parameter block is largely restatement; however it does add decision-relevant meaning, notably that minSeverity also suppresses vulnerability rows for billing purposes and that limit/offset drive paging (offset maps to nextOffset). It doesn't document the packages-vs-manifestText precedence beyond 'one required', which the schema already handles.
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 concrete verb and resource (parses manifests, checks OSV, returns per-package max severity/CVSS and fixed versions) and enumerates the exact manifest formats handled. It also names the sibling tools it is not (osv_query_packages, osv_get_vulnerability), so an agent can route without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit WHEN and WHEN NOT sections: use this for full project audits / CI-style reports, use osv_query_packages for a few exact versions, osv_get_vulnerability for one advisory. The cost trade-off is stated as the deciding factor, which is exactly what an agent needs to choose between a paid and free sibling.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
osv_get_vulnerabilityGet vulnerability advisory details (free, OSV.dev)ARead-onlyIdempotentInspect
WHAT: Fetch full OSV records (summary, details, aliases, severity vectors, affected ranges, fixed versions, references) for advisory IDs from api.osv.dev. WHEN: You have IDs (from osv_query_packages or a report) and need details or the fixed version. WHEN NOT: To find which vulns affect a package version (osv_query_packages) or a whole manifest (osv_audit_dependencies, paid). COST: Free; no token. Advisory IDs leave the client, are received by this Worker, and are forwarded to api.osv.dev. Nothing is sent to Apify. This Worker's code is designed not to store inputs or results. RETURNS: Paged "vulnerability" rows with the raw OSV record fields, or "error" rows (VULN_NOT_FOUND | OSV_FETCH_FAILED, see PARAMETERS). 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, results, or an Apify token. One HTTPS call per ID to api.osv.dev, in parallel; typically <2 s, 20 s timeout each. PARAMETERS: ids: each ID is fetched separately and yields exactly one row in input order, so one bad ID never fails the others: an unknown ID returns a VULN_NOT_FOUND row (not retryable), a network/5xx problem an OSV_FETCH_FAILED row (retryable - retry just that ID). Prefer the ecosystem/GHSA IDs returned by osv_query_packages (vuln_ids); a CVE-... ID only resolves when OSV indexes that CVE as its own record, otherwise look up the GHSA alias. IDs are matched as given (no trimming of URLs or prefixes), and duplicates are fetched twice. limit/offset only page the returned rows; every call re-fetches all given IDs, so for a next page resend the same ids with offset = nextOffset. EXAMPLE: {"ids":["GHSA-35jh-r3h4-6jhm"]} -> lodash command injection, fixed in 4.17.21.
| Name | Required | Description | Default |
|---|---|---|---|
| ids | Yes | Required. 1-25 OSV advisory IDs, 3-100 chars each, pattern ^[A-Za-z0-9][A-Za-z0-9._:-]{2,99}$ (letters, digits, . _ : -). Formats: GHSA-xxxx-xxxx-xxxx, PYSEC-YYYY-N, GO-YYYY-NNNN, RUSTSEC-YYYY-NNNN, CVE-YYYY-NNNNN. Example: ["GHSA-35jh-r3h4-6jhm","PYSEC-2021-9","GO-2022-0969"] | |
| limit | No | Optional page size: items to return, integer 1-25 (default 25). Example: 25 | |
| offset | No | Optional zero-based number of items to skip, integer 0-1000000 (default 0); pass nextOffset from the previous response, e.g. 100 |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context (summary counts, cost notes, ...) |
| count | Yes | Number of items in this page |
| items | Yes | Result rows for this page |
| total | Yes | Total items 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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/openWorld, so the bar is lower, yet the description adds substantial non-structured context: execution location (Cloudflare Worker, not caller's machine), data flow to api.osv.dev with nothing sent to Apify, no-storage design, one HTTPS call per ID in parallel, <2s typical with 20s per-call timeout. It also discloses partial-failure semantics (per-ID error rows, retryability) that annotations cannot express. No contradiction with any hint.
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?
Strongly front-loaded with labeled sections (WHAT/WHEN/WHEN NOT/COST/RETURNS/BEHAVIOR/PARAMETERS/EXAMPLE) and dense with information. Minor redundancy: 'no token' and the 'designed not to store inputs/results' claim appear in both COST and BEHAVIOR, which slightly dilutes an otherwise tightly packed definition.
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 3-parameter, partial-failure-prone network tool the description covers everything an agent needs: routing, cost/privacy posture, error taxonomy with retry guidance, pagination mechanics, and a concrete input/output example. An output schema also exists, so return-value detail is a bonus rather than a gap.
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% (baseline 3), but the description goes well beyond the schema: each ID yields exactly one row in input order, unknown IDs produce a non-retryable VULN_NOT_FOUND versus a retryable OSV_FETCH_FAILED, IDs are matched verbatim with no URL/prefix trimming, duplicates are fetched twice, and pagination requires resending the same ids with the returned nextOffset. These are operational semantics the schema cannot convey.
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 (Fetch) and resource (full OSV records) and enumerates the fields returned (summary, details, aliases, severity vectors, fixed versions, references). It is unambiguously distinguishable from osv_query_packages and osv_audit_dependencies, which the 'WHEN NOT' line names directly.
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 two-sided routing: 'WHEN: You have IDs (from osv_query_packages or a report)' and 'WHEN NOT: To find which vulns affect a package version (osv_query_packages) or a whole manifest (osv_audit_dependencies, paid).' Both alternatives are named with the condition that selects them, leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
osv_query_packagesCheck package versions for known vulnerabilities (free, OSV.dev)ARead-onlyIdempotentInspect
WHAT: Query the public OSV.dev database (api.osv.dev querybatch) for exact package versions; returns which are vulnerable and the advisory IDs. WHEN: Quick "is lodash 4.17.20 vulnerable?" checks for up to 200 packages; then osv_get_vulnerability for details of specific IDs. WHEN NOT: Parsing manifests/lockfiles, severity filtering, fixed/recommended versions in one shot -> osv_audit_dependencies (paid). COST: Free; no token. Package names and versions leave the client, are received by this Worker, and are forwarded to api.osv.dev. Nothing is sent to Apify. This Worker's code is designed not to store inputs or results. RETURNS: Paged "package" rows (vulnerable, vuln_ids) or per-entry INVALID_ECOSYSTEM "error" rows. Tool errors: OSV_FETCH_FAILED (retryable). 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, results, or an Apify token. One HTTPS request to api.osv.dev (Google-run public API) for all packages; typically <2 s, 20 s timeout. Results change only when OSV publishes advisories. PARAMETERS: packages: each entry is checked independently and returns exactly one row, in input order. An unknown ecosystem yields a per-entry INVALID_ECOSYSTEM error row while the other entries still run. A version range such as "^4.17.0" or ">=2" is not resolved and normally returns no vulnerabilities (a false "safe"), so pin versions from your lockfile first. Duplicate entries are queried and returned twice. limit/offset page the rows (all packages are sent to OSV in a single request); meta.vulnerable_count counts all packages, not only the page. EXAMPLE: {"packages":[{"ecosystem":"npm","name":"lodash","version":"4.17.20"}]} -> vulnerable true, vuln_ids ["GHSA-35jh-r3h4-6jhm",...].
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Optional page size: items to return, integer 1-200 (default 100). Example: 50 | |
| offset | No | Optional zero-based number of items to skip, integer 0-1000000 (default 0); pass nextOffset from the previous response, e.g. 100 | |
| packages | Yes | Required. 1-200 {ecosystem, name, version} objects, one exact version each. Example: [{"ecosystem":"npm","name":"lodash","version":"4.17.20"},{"ecosystem":"PyPI","name":"django","version":"3.2.0"}] |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context (summary counts, cost notes, ...) |
| count | Yes | Number of items in this page |
| items | Yes | Result rows for this page |
| total | Yes | Total items 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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/openWorld, and the description goes well beyond them: data-flow disclosure, single HTTPS request to api.osv.dev, <2s typical / 20s timeout, retryable OSV_FETCH_FAILED error code, and the fact that results change only when OSV publishes advisories. That is exactly the extra context the lower bar for annotated tools asks for.
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/COST/RETURNS/BEHAVIOR/PARAMETERS/EXAMPLE labeling makes it skimmable and front-loaded. It loses a point for redundancy between COST and BEHAVIOR, which both restate that inputs leave the client and that the Worker does not store inputs or results.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return-format detail is optional, yet the description still summarizes row shapes and error rows. Latency, failure modes, paging caveats, and the false-safe pitfall on version ranges are all covered, leaving nothing an agent needs to call this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline would be 3, but the description adds semantics the schema cannot express: each entry is checked independently and returns exactly one row in input order, unknown ecosystems yield per-entry INVALID_ECOSYSTEM rows without failing the batch, ranges like '^4.17.0' silently return a false 'safe', duplicates are returned twice, and meta.vulnerable_count is not page-scoped.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The WHAT line states a specific verb and resource (query the OSV.dev database for exact package versions) plus the concrete return payload (vulnerable flag and advisory IDs). It explicitly names the two sibling tools it is not: osv_get_vulnerability for details and osv_audit_dependencies for manifest parsing.
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?
Dedicated WHEN and WHEN NOT sections give the positive trigger (quick 'is lodash 4.17.20 vulnerable?' for up to 200 packages), the explicit exclusions (manifest/lockfile parsing, severity filtering, fixed versions) and route those cases to osv_audit_dependencies. 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.
pii_guard_documentsDeep PII/secret guard for documents and URLs (paid)ARead-onlyIdempotentInspect
WHAT: Runs the Fairquery Apify Actor llm-data-guard-pii-secrets: everything pii_scan_text does PLUS person-name/location NER, street addresses, public document URLs (txt/md/json/csv/HTML/PDF text layer, <= 10 MB) and restore mode (put originals back into LLM output using a placeholder map). WHEN: Only when you need NER names/addresses, files at URLs/PDFs, or the hosted pipeline. WHEN NOT: Plain text scans (pii_scan_text / pii_redact_text, free), restoring placeholders in an LLM answer (pii_restore_text, free, same result as restore mode), images (no OCR), email deliverability. COST: Paid, billed to YOUR Apify account via your APIFY_TOKEN: $1.50 per 1,000 documents + $0.20 per 1,000 findings. Example: 10 docs with 20 findings ~ $0.02. RETURNS: Paged "document" rows (findings masked; output_text in redact/restore modes) or "error" rows. Tool errors: APIFY_TOKEN_MISSING, APIFY_TOKEN_INVALID, APIFY_PAYMENT_REQUIRED, APIFY_ACTOR_RUN_FAILED, APIFY_TIMEOUT, INPUT_EMPTY. BEHAVIOR: Called from this Fairquery-hosted Worker, which forwards your input and Apify token to Apify and waits 20 s (APIFY_TIMEOUT if the run has not finished). This Worker's code is designed not to store them. The Actor runs in your Apify account and may fetch documentUrls. Up to 1000 texts / 100 URLs (10 MB each). PARAMETERS: texts and/or documentUrls: at least one non-empty (the schema marks both optional; neither -> INPUT_EMPTY, not billed); both may be combined and each text or URL becomes one billed document row. mode="restore" ignores all detector flags; a placeholder missing from placeholderMap stays as is. Cost scales with documents and findings, so narrow entityTypes to cut finding charges. limit/offset only page the returned rows: every call processes and bills the whole input again (also when only offset changes), so pick a limit (max 1000) that covers all rows instead of paging. EXAMPLE: {"documentUrls":["https://example.com/report.pdf"],"mode":"redact","detectNames":true}
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | Optional, one of "detect" | "redact" | "restore", default "redact". "detect" = findings only; "redact" = findings + output_text; "restore" = replace placeholders in texts with originals from placeholderMap. Example: "detect" | redact |
| limit | No | Optional page size: items to return, integer 1-1000 (default 100). Example: 50 | |
| texts | No | Optional if documentUrls is given (at least one of the two is required). 0-1000 documents as plain strings, each <= 1,000,000 chars; one billed document row each. Example: ["Mail jane@acme.com, key AKIAIOSFODNN7EXAMPLE"] | |
| offset | No | Optional zero-based number of items to skip, integer 0-1000000 (default 0); pass nextOffset from the previous response, e.g. 100 | |
| allowList | No | Optional, default []. 0-1000 exact values never flagged (whole value, case-insensitive). Example: ["support@yourco.com"] | |
| detectNames | No | Optional boolean, default false. true enables English person-name NER (type PERSON; lower precision, slower) | |
| entityTypes | No | Optional, default omitted = all enabled detectors. 0-40 type names from pii_list_detectors (case-insensitive), e.g. ["EMAIL","PERSON"]; PERSON/LOCATION/STREET_ADDRESS also need detectNames/detectAddresses | |
| entropyScan | No | Optional boolean, default false. true also flags unlabeled random-looking tokens (20+ chars) as HIGH_ENTROPY_STRING | |
| documentUrls | No | Optional if texts is given. 0-100 public http(s) URLs (<= 2000 chars) of txt/md/json/csv/HTML/PDF files (<= 10 MB each, PDF text layer only), fetched by the Actor in Apify cloud. Example: ["https://example.com/a.pdf"] | |
| placeholderMap | No | Optional; used only with mode="restore". Object mapping placeholder -> original value, as returned by an earlier redact call. Example: {"[EMAIL_1]":"jane@acme.com"} | |
| redactionStyle | No | Optional, one of "placeholder" | "mask" | "hash", default "placeholder"; used in mode="redact" only. placeholder = [EMAIL_1] (reversible), mask = j***@a***.com, hash = [EMAIL:3f2a9c0d1b7e] | placeholder |
| detectAddresses | No | Optional boolean, default false. true enables street-address patterns (STREET_ADDRESS) and place-name NER (LOCATION) | |
| returnPlaceholderMap | No | Optional boolean, default false. true adds placeholder_map (ORIGINAL values) to each row; only with mode="redact" and redactionStyle="placeholder" |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context (summary counts, cost notes, ...) |
| count | Yes | Number of items in this page |
| items | Yes | Result rows for this page |
| total | Yes | Total items 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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes far beyond the annotations: per-document and per-finding pricing with a worked example, billing routed to the caller's Apify account via APIFY_TOKEN, 20s wait with APIFY_TIMEOUT, enumerated tool error codes, data-handling note about the hosted Worker, input caps, and the critical warning that every call re-processes and re-bills the whole input even when only offset changes. Annotations only say readOnly/idempotent/openWorld; the cost and re-billing behavior is the real risk and the description discloses it.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Labeled sections (WHAT/WHEN/WHEN NOT/COST/RETURNS/BEHAVIOR/PARAMETERS/EXAMPLE) front-load the essentials with no filler. Slightly penalized because the cost-scaling point appears twice (COST and PARAMETERS) and the PARAMETERS block is a single dense run-on that is harder to scan than the rest.
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 13-parameter, paid, open-world tool this covers everything an agent needs: cost model, auth prerequisites, timeouts, error codes, size/count limits, paging pitfalls, and a concrete example. An output schema exists, so the brief RETURNS note is a bonus rather than a gap-filler.
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% (baseline 3), but the description adds semantics the schema cannot: texts and documentUrls are individually optional yet at least one is required (INPUT_EMPTY, not billed), both may be combined with one billed row each, mode="restore" ignores all detector flags, and unmapped placeholders pass through unchanged. The limit/offset paging warning is a genuine behavioral constraint tied to parameters.
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 the exact Actor (llm-data-guard-pii-secrets) and frames it as a superset of pii_scan_text: NER names/locations, street addresses, URL/PDF documents, and restore mode. That explicitly delineates it from the free sibling tools rather than restating the title.
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?
Has explicit WHEN (need NER names/addresses, URLs/PDFs, hosted pipeline) and WHEN NOT sections naming the specific alternatives (pii_scan_text, pii_redact_text, pii_restore_text, plus out-of-scope images and email deliverability). An agent can pick correctly without opening any schema.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pii_list_detectorsList PII/secret detectors and pricing (free)ARead-onlyIdempotentInspect
WHAT: Catalog of every PII and secret entity type, which ones the free tools run on this Worker (pii_scan_text / pii_redact_text) vs only in the paid Actor (NER names/locations, street addresses, URL/PDF documents), redaction styles, and the price of every paid tool in this server. WHEN: Before pii_scan_text / pii_redact_text to pick entityTypes, or to explain coverage and costs to a user. WHEN NOT: To actually scan text (pii_scan_text) or redact it (pii_redact_text). COST: Free; no token. RETURNS: One catalog object; local:true marks the detectors the free tools run (see PARAMETERS). 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, results, or an Apify token. No further network call; the catalog is compiled into this Worker, instant (<10 ms). No rate limits. PARAMETERS: None: call with an empty object {}; the catalog is always complete and cannot be filtered. Its output feeds other tools' parameters: copy detector "type" values verbatim into entityTypes of pii_scan_text / pii_redact_text (only rows with local:true can match there) or pii_guard_documents (all rows, incl. PERSON / LOCATION / STREET_ADDRESS); redaction_styles keys are the allowed redactionStyle values. EXAMPLE: {} -> {"detectors":[{"type":"AWS_ACCESS_KEY","category":"secret","local":true},...]}
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| detectors | Yes | |
| paid_pricing | Yes | |
| redaction_styles | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, closed-world, non-destructive, so the bar is lower — yet the description adds substantial context: it runs on a Cloudflare Worker, inputs leave the client, no inputs/results/token are stored, no further network call, <10 ms latency, no rate limits. This is exactly the kind of operational detail annotations cannot convey.
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/COST/RETURNS/BEHAVIOR/PARAMETERS/EXAMPLE sections make it scannable and front-loaded, with no filler sentences. It is on the long side for a zero-parameter catalog call — the BEHAVIOR block is dense — which keeps it just short of 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?
Output schema exists, so return values need not be spelled out, yet the description still summarizes the return shape and explains the meaning of local:true and the redaction_styles keys. Combined with the cost and latency notes, an agent has everything needed to call this correctly and use its 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?
Zero-parameter tool (baseline 4), but the description goes beyond: it states the call must use an empty object {}, that the catalog is complete and cannot be filtered, and how output values map into other tools' parameters (detector 'type' into entityTypes, redaction_styles keys into redactionStyle). That directly reduces downstream invocation errors.
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 (catalog of every PII and secret entity type) and enumerates the extra dimensions it covers: local vs paid-Actor coverage, redaction styles, and per-tool pricing. An agent can distinguish this from pii_scan_text / pii_redact_text without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'WHEN' (before pii_scan_text / pii_redact_text to pick entityTypes, or to explain coverage and costs) and 'WHEN NOT' (to actually scan or redact), each naming the sibling tool that should be used instead. The selection rule is fully determined.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pii_redact_textRedact PII and secrets (free, on this Worker)ARead-onlyIdempotentInspect
WHAT: Return sanitized copies of texts with every local PII/secret finding replaced (placeholder [EMAIL_1], mask, or salted hash). WHEN: Right before sending user data to an LLM/third party. Use returnPlaceholderMap=true if you need to restore originals in the LLM answer later. WHEN NOT: For a findings report only use pii_scan_text. For names/addresses NER, URLs or PDFs use pii_guard_documents (paid). COST: Free; no token. RETURNS: One "redacted_document" row per text with output_text; meta.placeholder_map only when requested. Errors: INPUT_TOO_LARGE. BEHAVIOR: Same input gives the same output except redactionStyle "hash" (random per-call salt). 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, results, or an Apify token. No further network call; texts are not forwarded. Milliseconds. PARAMETERS: texts: one element per document, 500,000 chars in total; the same value gets the same placeholder in every document of one call ([EMAIL_1] everywhere), but numbering restarts on each call. redactionStyle: "placeholder" is the only reversible style; "mask" keeps a few characters for human review; "hash" output differs between calls (random salt), so do not compare hashes across calls. returnPlaceholderMap: only has an effect with redactionStyle="placeholder" (ignored for mask/hash); the map in meta.placeholder_map holds the ORIGINAL values, so keep it out of the LLM prompt and restore answers with pii_restore_text (free, on this Worker). entityTypes / allowList / entropyScan / defaultPhoneRegion: identical semantics to pii_scan_text (filter after detection, whole-value case-insensitive allow-list). limit/offset page whole documents (one item per text, default 10 per page), not findings. EXAMPLE: {"texts":["Call +1 415 555 0100 or mail jane@acme.com"]} -> output_text "Call [PHONE_1] or mail [EMAIL_1]".
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Optional page size: items to return, integer 1-50 (default 10). Example: 10 | |
| texts | Yes | Required. 1-50 documents (prompt, chat message, log, file content), each <= 200,000 chars, total <= 500,000 chars. Example: ["Mail jane@acme.com, key AKIAIOSFODNN7EXAMPLE"] | |
| offset | No | Optional zero-based number of items to skip, integer 0-1000000 (default 0); pass nextOffset from the previous response, e.g. 100 | |
| allowList | No | Optional, default [] (nothing exempt). 0-500 exact values (<= 500 chars each) that are never flagged; whole-value, case-insensitive match, not substring. Example: ["support@yourco.com","192.0.2.1"] | |
| entityTypes | No | Optional, default omitted = every free-tool detector (pii_list_detectors rows with local:true). 0-40 detector type names (<= 40 chars, case-insensitive), e.g. ["EMAIL","CREDIT_CARD","AWS_ACCESS_KEY"]. Valid values: the "type" field of those rows; unknown names match nothing (no error) | |
| entropyScan | No | Optional boolean, default false. true also flags unlabeled random-looking tokens of 20-200 chars as HIGH_ENTROPY_STRING (catches unknown key formats; may flag hashes/UUIDs). Example: true | |
| redactionStyle | No | Optional, one of "placeholder" | "mask" | "hash", default "placeholder". "placeholder" = [EMAIL_1] (reversible); "mask" = j***@a***.com, **** **** **** 1111; "hash" = [EMAIL:3f2a9c0d1b7e] (salted sha256 prefix, random per-call salt). Example: "mask" | placeholder |
| defaultPhoneRegion | No | Optional, default "US". ISO 3166-1 alpha-2 code (2 letters, pattern ^[A-Za-z]{2}$, case-insensitive) used only for phone numbers written without a +country prefix. Examples: "US", "DE", "GB" | US |
| returnPlaceholderMap | No | Optional boolean, default false. true (placeholder style only) adds meta.placeholder_map {"[EMAIL_1]":"jane@acme.com"} containing the ORIGINAL sensitive values. Example: true |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context (summary counts, cost notes, ...) |
| count | Yes | Number of items in this page |
| items | Yes | Result rows for this page |
| total | Yes | Total items 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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well past the annotations: discloses determinism (identical output except hash style with per-call random salt), where computation happens (Fairquery-hosted Cloudflare Worker, inputs leave the client), the no-storage/no-forwarding guarantee, latency, and the INPUT_TOO_LARGE error. One small tension: idempotentHint=true while hash output varies between calls — the description explains this, so it is disambiguation rather than a contradiction, but it slightly undercuts the annotation's guarantee.
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 with labeled sections (WHAT/WHEN/WHEN NOT/COST/RETURNS/BEHAVIOR/PARAMETERS/EXAMPLE), so an agent can stop reading early. It is long for a description, but nearly every line carries decision-relevant information; the COST and hosting lines are the least essential.
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?
Nine parameters, an enum, and an output schema are all accounted for: return shape, placeholder_map location, error case, and the restore path via pii_restore_text are documented. Nothing an agent needs to call this correctly on the first try 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 already 100%, yet the description still adds non-obvious semantics: placeholder numbering is stable across documents within a call but restarts per call, hash values must not be compared across calls, returnPlaceholderMap is silently ignored for mask/hash and contains ORIGINAL values that must be kept out of the prompt, and limit/offset page documents not findings.
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 an explicit verb+resource+output: 'Return sanitized copies of texts with every local PII/secret finding replaced', and names the exact placeholder/mask/hash forms. It is unambiguously distinguishable from pii_scan_text (findings report) and pii_guard_documents (NER/URLs/PDFs) which are called out by name.
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 ('right before sending user data to an LLM/third party') plus an explicit WHEN NOT that routes to two named siblings with the conditions that select each. It also gives an internal usage decision (returnPlaceholderMap=true when you need to restore later).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pii_restore_textRestore placeholders with original values (free, on this Worker)ARead-onlyIdempotentInspect
WHAT: Put the original values back into texts (typically an LLM answer) that contain placeholders such as [EMAIL_1], using the placeholder map from pii_redact_text or pii_guard_documents. WHEN: After an LLM or third party answered a prompt you sanitized with pii_redact_text (redactionStyle "placeholder", returnPlaceholderMap=true), before showing the answer to the user. WHEN NOT: To redact (pii_redact_text) or find PII (pii_scan_text). Masked and hashed values cannot be restored; only placeholder style is reversible. COST: Free; no token. RETURNS: One "restored_document" row per text with output_text, replacements and unknown_placeholders. Errors: INPUT_TOO_LARGE. 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, results, or an Apify token. No further network call; the placeholder map is not forwarded. Milliseconds. Max 50 texts, 200,000 chars each, 500,000 total; map up to 10,000 entries. PARAMETERS: placeholderMap: pass meta.placeholder_map unchanged; every key is replaced wherever it occurs exactly (case-sensitive, longest key first, one pass, so a restored value is never replaced again). unknown_placeholders lists [TYPE_N]-shaped tokens left in the text that the map does not contain (the LLM invented or altered them); they stay as they are. A placeholder the LLM rewrote (e.g. "EMAIL_1" without brackets) is not restored. The output contains the ORIGINAL sensitive values: show it to the user, do not send it back to the LLM. limit/offset page whole documents (default 10 per page). EXAMPLE: {"texts":["I will email [EMAIL_1] today."],"placeholderMap":{"[EMAIL_1]":"jane@acme.com"}} -> output_text "I will email jane@acme.com today.", replacements 1.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Optional page size: items to return, integer 1-50 (default 10). Example: 10 | |
| texts | Yes | Required. 1-50 documents (prompt, chat message, log, file content), each <= 200,000 chars, total <= 500,000 chars. Example: ["Mail jane@acme.com, key AKIAIOSFODNN7EXAMPLE"] | |
| offset | No | Optional zero-based number of items to skip, integer 0-1000000 (default 0); pass nextOffset from the previous response, e.g. 100 | |
| placeholderMap | Yes | Required. Object placeholder -> original value, as returned in meta.placeholder_map by pii_redact_text (or placeholder_map by pii_guard_documents); keys 1-200 chars, values <= 10,000 chars, <= 10,000 entries. Example: {"[EMAIL_1]":"jane@acme.com","[PHONE_1]":"+14155550100"} |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context (summary counts, cost notes, ...) |
| count | Yes | Number of items in this page |
| items | Yes | Result rows for this page |
| total | Yes | Total items 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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/idempotent annotations: it discloses that execution happens on a hosted Cloudflare Worker so inputs leave the client, that the Worker is designed not to store inputs/results/tokens, that no further network call occurs and the map isn't forwarded, plus latency, size caps (50 texts, 200k chars each, 10k map entries) and the INPUT_TOO_LARGE error. It also flags the sensitive nature of the output and warns not to send it back to the LLM.
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?
Strongly front-loaded with WHAT/WHEN/WHEN NOT/COST/RETURNS labels that make it scannable despite its length. It is dense and repeats a few facts already in the schema (size limits, example map), so it is a touch longer than strictly necessary, but no section is filler.
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 nested-object, security-sensitive tool it covers everything an agent needs: source of the map, reversibility limits, replacement semantics, pagination, error case, output shape (restored_document rows with output_text, replacements, unknown_placeholders) and a worked example — even though an output schema exists.
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 already 100%, yet the description adds real semantics: placeholderMap should be passed as meta.placeholder_map unchanged, keys are matched exactly/case-sensitively with longest-key-first in a single pass so restored values are never re-replaced, and it explains what unknown_placeholders means and why an LLM-rewritten placeholder won't restore. It also clarifies that limit/offset page whole documents (default 10), which the schema only partially conveys.
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: putting original values back into texts containing placeholders like [EMAIL_1]. It explicitly names the source of the map (pii_redact_text / pii_guard_documents) and its inverse siblings (pii_redact_text, pii_scan_text), so an agent can distinguish it from every other PII tool without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Has explicit WHEN (after an LLM answered a sanitized prompt, before showing the user) and WHEN NOT (not for redacting or scanning; masked/hashed values are not reversible — only placeholder style is). It names the concrete preconditions (redactionStyle "placeholder", returnPlaceholderMap=true), leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pii_scan_textScan text for PII and secrets (free, on this Worker)ARead-onlyIdempotentInspect
WHAT: Detect PII (emails, phones, US SSNs, Luhn-valid cards, mod-97 IBANs, UK NINOs, IPv4/IPv6) and secrets (AWS/GitHub/GitLab/Slack/Stripe/Google/OpenAI/Anthropic/HF/npm/SendGrid/Twilio/Azure keys, JWTs, private keys, URL credentials, key=value secrets) in text. WHEN: Before sending prompts, tickets, logs or files to an LLM or third party; to audit text for leaked credentials. Findings are paged across all documents. WHEN NOT: To get sanitized text use pii_redact_text. For person names/addresses (NER), public URLs or PDFs use pii_guard_documents (paid). Not for images (no OCR). COST: Free. Regex and validators on this Worker; no APIFY_TOKEN. RETURNS: Paged findings across all documents, each with a masked preview; raw values are never returned. Errors: INPUT_TOO_LARGE. 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, results, or an Apify token. No further network call; the text is not forwarded to another service. Milliseconds. PARAMETERS: texts: one array element per document; each finding carries doc_index = the element's 0-based position, and the 500,000-char cap applies to the SUM of all elements (over it -> INPUT_TOO_LARGE; split into several calls). entityTypes: filters findings after detection; names are case-insensitive, unknown names silently match nothing, and NER-only types (PERSON, LOCATION, STREET_ADDRESS) never match here. allowList: compared with the whole matched value, case-insensitively (allow "support@yourco.com", not "yourco.com"). entropyScan: only adds HIGH_ENTROPY_STRING findings; it never removes others. defaultPhoneRegion: affects only national-format numbers such as "030 1234567" (needs "DE"); "+49 ..." numbers parse with any region. limit/offset page the flat findings list across all documents, while meta.documents always summarises every document; fetch the next page with offset = nextOffset until hasMore is false. EXAMPLE: {"texts":["Mail jane@acme.com, key AKIAIOSFODNN7EXAMPLE"]} -> 2 findings: EMAIL (masked "j***@a***.com"), AWS_ACCESS_KEY; risk_level "critical".
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Optional page size: items to return, integer 1-1000 (default 100). Example: 50 | |
| texts | Yes | Required. 1-50 documents (prompt, chat message, log, file content), each <= 200,000 chars, total <= 500,000 chars. Example: ["Mail jane@acme.com, key AKIAIOSFODNN7EXAMPLE"] | |
| offset | No | Optional zero-based number of items to skip, integer 0-1000000 (default 0); pass nextOffset from the previous response, e.g. 100 | |
| allowList | No | Optional, default [] (nothing exempt). 0-500 exact values (<= 500 chars each) that are never flagged; whole-value, case-insensitive match, not substring. Example: ["support@yourco.com","192.0.2.1"] | |
| entityTypes | No | Optional, default omitted = every free-tool detector (pii_list_detectors rows with local:true). 0-40 detector type names (<= 40 chars, case-insensitive), e.g. ["EMAIL","CREDIT_CARD","AWS_ACCESS_KEY"]. Valid values: the "type" field of those rows; unknown names match nothing (no error) | |
| entropyScan | No | Optional boolean, default false. true also flags unlabeled random-looking tokens of 20-200 chars as HIGH_ENTROPY_STRING (catches unknown key formats; may flag hashes/UUIDs). Example: true | |
| defaultPhoneRegion | No | Optional, default "US". ISO 3166-1 alpha-2 code (2 letters, pattern ^[A-Za-z]{2}$, case-insensitive) used only for phone numbers written without a +country prefix. Examples: "US", "DE", "GB" | US |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context (summary counts, cost notes, ...) |
| count | Yes | Number of items in this page |
| items | Yes | Result rows for this page |
| total | Yes | Total items 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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well past the annotations: states the tool runs on a third-party hosted Worker (Cloudflare), that inputs leave the client, that no storage or Apify token is used, that there is no further network call, and that latency is milliseconds. It also names the failure mode INPUT_TOO_LARGE. This is exactly the context readOnlyHint/idempotentHint cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Labeled sections (WHAT/WHEN/WHEN NOT/COST/RETURNS/BEHAVIOR/PARAMETERS/EXAMPLE) front-load the essentials and the long detector list is bullet-like rather than prose, so density is justified. It is on the long side, but nearly every clause carries non-redundant 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?
For a 7-param, 3rd-party-hosted scanning tool this covers everything an agent needs: privacy posture, paging semantics (offset = nextOffset until hasMore false, meta.documents always summarizes all documents), error handling, and a worked example. The output schema exists, yet the description adds the paging/masking context it wouldn't otherwise carry.
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 already 100%, yet the description adds genuine meaning: doc_index maps findings to 0-based array positions, the 500k cap applies to the SUM of elements (schema only shows per-item 200k/maxItems 50), entityTypes filters post-detection and silently no-matches unknown or NER-only names, allowList is whole-value case-insensitive, entropyScan only adds findings, and defaultPhoneRegion only affects national-format numbers.
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 an explicit verb+resource (detect PII and secrets in text) and enumerates the concrete detector families (emails, SSNs, Luhn cards, mod-97 IBANs, JWT, cloud keys), so the scope is unambiguous. It is clearly separable from pii_redact_text and pii_guard_documents, which are named.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit WHEN (before sending prompts/tickets/logs to an LLM or third party; auditing leaks), WHEN NOT with named alternatives (pii_redact_text for sanitized text; pii_guard_documents for NER/public URLs/PDFs), plus an explicit exclusion (no images/OCR) and cost note. 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.
sanctions_list_sourcesList covered sanctions lists (free)ARead-onlyIdempotentInspect
WHAT: Static list of the official sanctions lists sanctions_screen_names checks (id, name, authority, download URL), the score scale and the legal disclaimer. WHEN: To choose the lists parameter, cite sources in a report, or explain coverage before paying. WHEN NOT: To screen a name (sanctions_screen_names, paid). It does not download lists or report freshness. COST: Free; no token. RETURNS: One static object, including the disclaimer to show with screening results. 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, results, or an Apify token. No further network call; static catalog compiled into this Worker, instant. No rate limits. PARAMETERS: None: call with an empty object {}; the result is static and cannot be filtered. Its output feeds sanctions_screen_names: each sources[].id (OFAC_SDN, OFAC_CONS, EU, UK, UN) is a valid value for its lists parameter, and score_scale explains how its minScore threshold is applied. EXAMPLE: {} -> {"sources":[{"id":"OFAC_SDN","authority":"US Treasury OFAC",...},...]}
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| pricing | Yes | |
| sources | Yes | |
| disclaimer | Yes | |
| score_scale | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond annotations, it discloses execution context (Fairquery-hosted Cloudflare Worker), data handling (inputs leave the client but are designed not to be stored), network behavior (no further network call), latency (instant), cost/token requirements, and rate limits. These are exactly the behavioral traits an agent needs beyond readOnly/idempotent 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?
The WHAT/WHEN/WHEN NOT/COST/RETURNS/BEHAVIOR/PARAMETERS/EXAMPLE structure is front-loaded and easy to scan. It is somewhat long and repeats the free/disclaimer point across sections, but the density is justified by the need to prevent misfires.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given a static zero-parameter tool with rich annotations and an output schema, the definition is complete: it covers purpose, invocation, output shape, disclaimer handling, and how the result relates to the sibling screening 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?
With zero parameters, the baseline is 4. The description appropriately states that it must be called with an empty object and cannot be filtered, and it adds useful cross-tool semantics by explaining that each sources[].id feeds the lists parameter of sanctions_screen_names.
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 description states a specific resource (official sanctions lists catalogue) and distinguishes it explicitly from the paid sibling tool sanctions_screen_names. An agent can tell exactly what this tool does 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It provides explicit WHEN and WHEN NOT guidance, naming sanctions_screen_names as the alternative and stating the condition that selects it (screening a name). It also clarifies that this tool does not download lists or report freshness.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sanctions_screen_namesScreen names against OFAC/EU/UK/UN sanctions (paid)ARead-onlyIdempotentInspect
WHAT: Runs the Fairquery Apify Actor sanctions-watchlist-screener: fuzzy-match people/organisations against OFAC SDN, OFAC Consolidated, EU, UK and UN lists from the official sources (re-downloaded when the cached copy in your Apify account is older than 6 h; every row reports each list's publication date); optional country/DOB filters. WHEN: KYC/KYB onboarding, vendor/counterparty checks, bulk re-screening. Put all names in ONE call (each call is one run with its own start fee; list loading takes ~2-5 s from the cache, or 10-40 s, up to ~60 s, when lists are re-downloaded). This Worker waits 20 s, so a re-download that runs longer returns APIFY_TIMEOUT. WHEN NOT: PEP/adverse media, certified compliance decisions. For coverage info use free sanctions_list_sources. Leads that also have an email to validate: trust_screen_lead (one merged decision). COST: Paid, billed to YOUR Apify account via your APIFY_TOKEN: $3.00 per 1,000 names + $7.00 per 1,000 runs. 200 names in one call ~ $0.61. RETURNS: Paged "screening" rows (status potential_match | no_match, matches[] best first, lists_unavailable) or "error" rows. Tool errors: INPUT_EMPTY, APIFY_TOKEN_MISSING, APIFY_TOKEN_INVALID, APIFY_PAYMENT_REQUIRED, APIFY_ACTOR_RUN_FAILED, APIFY_TIMEOUT. Not a certified screening service. Automated fuzzy matching against public lists; false positives and misses are possible. Review every potential match manually and consult official sources/legal counsel before acting. BEHAVIOR: Results are stable for a given list snapshot. Called from this Fairquery-hosted Worker, which forwards your input and Apify token to Apify and waits 20 s (APIFY_TIMEOUT if the run has not finished). This Worker's code is designed not to store them. The Actor runs in your account. Up to 10,000 names. A list re-download can outlast the 20 s wait. PARAMETERS: names and/or queries: at least one non-empty (the schema marks both optional; neither -> INPUT_EMPTY, not billed). Both may be combined; every entry is billed once, so use names for bare strings and queries when you have country/dateOfBirth/entityType or want your own id echoed. In queries, dateOfBirth removes listed individuals born more than 1 year apart, while country never removes matches (it only sets country_match); entityType "entity" covers organisations, vessels and aircraft. lists: omit to screen all five; a list that cannot be downloaded is reported in lists_unavailable instead of failing the run. minScore: a match is returned only if its 0-100 similarity is >= minScore; 85 balances recall and noise, 90-95 for large batches, 75-80 for transliterated names; it does not change the price. maxMatchesPerName caps matches[] only; match_count still reports all matches. limit/offset only page the returned rows: every call screens and bills all names again (also when only offset changes), so set limit (max 1000) to cover all rows instead of paging. EXAMPLE: {"queries":[{"name":"Banco Nacional de Cuba","entityType":"entity"}],"minScore":90} -> status potential_match (OFAC_SDN).
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Optional page size: items to return, integer 1-1000 (default 100). Example: 50 | |
| lists | No | Optional, default all five. Any subset of "OFAC_SDN" | "OFAC_CONS" | "EU" | "UK" | "UN" (ids from sanctions_list_sources). Example: ["OFAC_SDN","EU"] | |
| names | No | Optional if queries is given (at least one is required). 0-10000 plain person/organisation names, 1-300 chars each. Example: ["Acme Trading LLC","Banco Nacional de Cuba"] | |
| offset | No | Optional zero-based number of items to skip, integer 0-1000000 (default 0); pass nextOffset from the previous response, e.g. 100 | |
| queries | No | Optional if names is given. 0-10000 {name, id?, country?, dateOfBirth?, entityType?} objects. Example: [{"name":"Ali Khamenei","id":"cust-42","country":"IR","dateOfBirth":"1939","entityType":"individual"}] | |
| minScore | No | Optional integer 60-100, default 85. Minimum 0-100 fuzzy name-similarity score for a match; 90+ = fewer false positives, ~80 = more recall. Example: 90 | |
| maxMatchesPerName | No | Optional integer 1-100, default 10. Max entries in matches[] per name (best first); match_count still reports the total. Example: 3 |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context (summary counts, cost notes, ...) |
| count | Yes | Number of items in this page |
| items | Yes | Result rows for this page |
| total | Yes | Total items 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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations, the description discloses paid Apify billing, token requirements, cache re-download timing, the 20 s Worker timeout, partial list failures via lists_unavailable, stable results per snapshot, and false-positive/miss limitations. It also states the Worker is designed not to store credentials/input.
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?
Although long, the definition is front-loaded and organized under WHAT, WHEN, WHEN NOT, COST, RETURNS, BEHAVIOR, PARAMETERS, and EXAMPLE. Every section carries operational information needed to avoid incorrect or costly calls.
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 paid, open-world screening tool with seven parameters and an output schema, the description covers invocation rules, billing, authentication, timeouts, error codes, result stability, and compliance caveats. It is complete enough for an agent to call it correctly and interpret the risks.
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 already 100%, but the description adds important semantics: names vs queries billing, dateOfBirth filtering behavior, country never filtering matches, entityType meaning, minScore tuning guidance, maxMatchesPerName capping only matches[], and the critical limit/offset billing caveat.
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 first line states a specific verb and resource: runs fuzzy sanctions screening for people/organisations against OFAC SDN, OFAC Consolidated, EU, UK and UN lists. It explicitly distinguishes itself from nearby tools by naming sanctions_list_sources and trust_screen_lead.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The WHEN section gives concrete use cases (KYC/KYB onboarding, vendor checks, bulk re-screening) and instructs putting all names in one call. The WHEN NOT section names alternatives for PEP/adverse media, list coverage, and email-bearing leads, leaving little to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
trust_screen_leadScreen leads: email deliverability + sanctions (paid)ARead-onlyIdempotentInspect
WHAT: One call per batch of leads/counterparties: runs email_validate_deliverability on their emails and sanctions_screen_names on their person/company names IN PARALLEL, then merges results per lead with an overall decision: block (invalid email or sanctions potential_match), review (risky email), pass. WHEN: Onboarding customers/vendors, qualifying inbound leads, CRM hygiene where both email quality and sanctions exposure matter. WHEN NOT: Email only -> email_validate_deliverability; names only -> sanctions_screen_names (same per-item prices, no merge step). Free pre-filter first: email_check_syntax. Use this tool only when a lead needs both checks and one decision. COST: Paid, billed to YOUR Apify account via your APIFY_TOKEN: $0.79 per 1,000 leads with email + $3.00 per 1,000 leads with name (= $3.79 per 1,000 full leads) + $7.00 per 1,000 calls for the sanctions run. 100 full leads ~ $0.39. RETURNS: Paged "lead" rows (decision pass | review | block with reasons[]; sanctions_matches holds the top 3); meta.summary counts by decision. Partial failure: the affected check shows "unavailable" and meta.errors lists codes. Tool errors: INPUT_EMPTY, APIFY_TOKEN_MISSING. BEHAVIOR: No email is sent. Called from this Fairquery-hosted Worker, which forwards your input and Apify token to Apify and waits 20 s (APIFY_TIMEOUT if the run has not finished). This Worker's code is designed not to store them. Runs the email and sanctions Apify Actors in parallel (2 runs per call) in your account. Sanctions lists come from a cache at most 6 h old, or are downloaded; a download can outlast the 20 s wait. PARAMETERS: leads: each record needs email and/or name (if no lead has either -> INPUT_EMPTY, nothing billed); a lead with only an email skips sanctions (sanctions_status "not_checked") and one with only a name skips email (email_verdict "not_checked"), so a lead is billed for each check it gets (prices under COST), plus the per-call sanctions run fee when any lead has a name. Identical emails across leads are validated and billed once. ref joins sanctions results back to the lead, so keep refs unique (default = the lead's 0-based index as a string). country and entityType apply to that lead's sanctions check only: entityType filters candidates, while country only annotates matches (never removes them). minScore: sanctions similarity threshold; default 88 here (stricter than the 85 of sanctions_screen_names) to keep block decisions low-noise; each name returns at most its top 3 matches. allowFreeProviders=false turns gmail.com-type addresses into email_verdict "risky", i.e. decision "review" (never "block"). Decision precedence: any sanctions potential_match or invalid email -> block; else risky email or an incomplete check -> review; else pass. limit/offset page the merged lead rows, but every call re-runs and re-bills both checks for all leads (also when only offset changes), so set limit (max 1000) to cover all leads; meta.summary counts all leads. EXAMPLE: {"leads":[{"ref":"L1","email":"cfo@acme.com","name":"Acme Trading LLC","country":"US"}]} -> {decision:"pass",email_verdict:"valid",sanctions_status:"no_match"}
| Name | Required | Description | Default |
|---|---|---|---|
| leads | Yes | Required. 1-1000 lead objects {ref?, email?, name?, country?, entityType?}; each needs email and/or name. Example: [{"ref":"L1","email":"cfo@acme.com","name":"Acme Trading LLC","country":"US","entityType":"entity"}] | |
| limit | No | Optional page size: items to return, integer 1-1000 (default 100). Example: 50 | |
| offset | No | Optional zero-based number of items to skip, integer 0-1000000 (default 0); pass nextOffset from the previous response, e.g. 100 | |
| minScore | No | Optional integer 60-100, default 88. Minimum 0-100 fuzzy name-similarity for a sanctions potential_match (which blocks the lead). Example: 92 | |
| allowFreeProviders | No | Optional boolean, default true. false makes free webmail (gmail.com, outlook.com, ...) email_verdict "risky" -> decision "review"; recommended for B2B |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | Tool-specific context (summary counts, cost notes, ...) |
| count | Yes | Number of items in this page |
| items | Yes | Result rows for this page |
| total | Yes | Total items 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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/openWorld, and the description goes well beyond them: exact price model, that billing hits the caller's own Apify account, that pagination does NOT avoid re-running and re-billing, the 20s Worker-side wait with APIFY_TIMEOUT, per-check 'unavailable' degradation with meta.errors, sanctions cache staleness (up to 6h, download may exceed the wait), and that no email is actually sent. Minor tension worth noting: idempotentHint=true while the description says every call incurs new charges, though results are unchanged.
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?
Heavy but well-engineered: labeled WHAT/WHEN/WHEN NOT/COST/RETURNS/BEHAVIOR/PARAMETERS/EXAMPLE headings front-load scope before cost and mechanics, and the worked example anchors the contract. It loses a point because the PARAMETERS block retreads schema-level details (max lengths, enums, defaults) and some sentences pack multiple rules together at the cost of skimmability.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, yet the description still supplies the essentials an agent needs to interpret it: row shape (decision + reasons[] + sanctions_matches capped at 3), meta.summary as an all-leads count rather than a page count, and the partial-failure signal. Combined with the stated error codes and the paging re-bill warning, nothing material for correct invocation 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 already 100%, so a 3 would be the floor, but the description adds real decision semantics: ref uniqueness rationale (joins sanctions results back to the lead), that country only annotates and never filters while entityType does filter, that minScore defaults to 88 here versus 85 in sanctions_screen_names, and that allowFreeProviders=false can only produce 'review', never 'block'. It also states the billing consequence of supplying only one of email/name, which 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?
The WHAT section states a specific compound verb (runs two named checks in parallel, merges per lead) on a specific resource (batch of leads/counterparties) and even names the decision vocabulary it produces (block/review/pass). It explicitly distinguishes itself from the three nearest siblings (email_validate_deliverability, sanctions_screen_names, email_check_syntax), so an agent can place 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 / WHEN NOT / COST block is explicit routing guidance: it names the exact condition that selects each alternative ('Email only -> email_validate_deliverability; names only -> sanctions_screen_names') and adds a free pre-filter step. The closing rule 'Use this tool only when a lead needs both checks and one decision' gives a hard boundary rather than an implied one.
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.
20 tool updates
- First observed
email_check_syntax - First observed
email_validate_deliverability - First observed
jobs_check_open - First observed
jobs_find_board - First observed
jobs_get_board - First observed
jobs_get_job - First observed
jobs_list_seed_boards - First observed
jobs_search_companies - First observed
mcp_server_precheck - First observed
osv_audit_dependencies - First observed
osv_get_vulnerability - First observed
osv_query_packages - First observed
pii_guard_documents - First observed
pii_list_detectors - First observed
pii_redact_text - First observed
pii_restore_text - First observed
pii_scan_text - First observed
sanctions_list_sources - First observed
sanctions_screen_names - First observed
trust_screen_lead
Related MCP Connectors
Contract and agent-package scans, phishing URL and OFAC checks, domain and header checks.
SEC red-flag alerts & company dossiers, token risk, wallet checks, x402 preflight, scraping, OFAC.
Sanctions Screening MCP — screen people, companies, vessels, and aircraft
Screen names against OFAC, EU and UN lists; check CVEs, domain DNS and WHOIS, and public tenders
Related MCP Servers
- AlicenseAqualityAmaintenanceAgent trust & data safety MCP: PII/secret redaction, email validation, sanctions screening, and OSV vulnerability checks for AI agents.2130MIT
- FlicenseNot gradedqualityDmaintenanceEnables screening companies or people against OFAC, UN, EU and UK sanctions lists, looking up SEC EDGAR records, checking domain age and email records, and compiling due-diligence reports with source links and a clear, review, or hold verdict.-

teralynk-mcp-serverofficial
AlicenseAqualityCmaintenance102-tool MCP server for AI-native compliance. PII scanning, HIPAA/GDPR/PCI-DSS/SOX/CCPA automation, file intelligence, workflow execution, audit trails, security events, and team management.137 npmMIT- AlicenseNot gradedqualityBmaintenanceEnables agents and applications to scan text streams for personally identifiable information, financial account numbers, JWT tokens, and cryptographic keys, returning sanitized output with deterministic zero-leakage masking. It runs as a zero-dependency MCP stdio server or importable Python module so regulatory compliance policies (GDPR/HIPAA-style) can be enforced on data before it leaves a workflow.7MIT
Glama MCP Gateway
Add one secure layer between your agents and this server.