Skip to main content
Glama
fairquery-contact-enricher

fairquery-contact-enricher

Official

Enrich domains with public business contacts

enrich_domains
Read-onlyIdempotent

Crawl company domains to extract public contact data: role/business emails, phones, social profiles, addresses, and company descriptions for lead or CRM enrichment.

Instructions

WHAT: For each company domain, crawl the homepage plus up to N contact/about/imprint pages (plain HTTP, robots.txt respected, polite per-site delay) and return one row with company_name, description, business/role emails (MX, disposable and typo validated), phones (E.164), social profiles (LinkedIn company, X, Facebook, Instagram, YouTube, GitHub...), postal address and mail-domain info. WHEN TO USE: enriching a lead/CRM list of company websites with public contact data; finding the official contact email or address of a business. WHEN NOT TO USE: to clean/dedupe a messy list first or preview what will be billed, call normalize_domains (free). Not for finding personal emails of individuals, LinkedIn people search, or JavaScript-only sites that need a browser. COST: paid. Runs Apify Actor fairquery/website-contact-company-enricher with your APIFY_TOKEN; pay-per-event "domain-enriched": $0.003 per domain that returns a result row. Unreachable, blocked or invalid domains are free. 100 domains ~ $0.30. ~2-10 s per domain. RETURNS: one type="domain" row per enriched domain (fields are null or empty when not published on the crawled pages) or free type="error" rows (code, message, retryable); with waitForFinish=false, items is empty and run holds run_id for get_enrichment_results. PARAMETERS: domains: entries are split on commas, semicolons and newlines, then each is reduced to its hostname (scheme, path, port and a leading "www." removed; for an email the part after @ is used; other subdomains are kept, so "shop.acme.com" and "acme.com" are separate) and deduplicated, so you pay once per unique enriched domain; unparseable entries give free INVALID_DOMAIN rows (max 1000 domains per run; extras get a free MAX_DOMAINS_EXCEEDED row). Run normalize_domains first to preview exactly what will be billed (billable_count, estimated_max_cost_usd). waitForFinish=false returns at once with run.run_id and the run keeps going in your Apify account (same price); use it for lists over ~100 domains, which may not finish within this call's 280 s wait, then poll get_enrichment_results. maxPagesPerDomain changes run time and coverage, never the price; 1 = homepage only, 3-5 usually finds the contact/imprint page. emailScope only filters which found addresses are returned: "role" keeps generic mailboxes (info@, sales@, support@...) on any domain, "business" adds named addresses on the company's own domain, "all" also keeps third-party/personal addresses - choose the narrowest scope your lawful basis allows. validateEmails=false skips only the DNS part: disposable/typo checks still run locally, has_mx and mail_domain become null and clean addresses get verdict "unknown" instead of "valid"; checkSpfDmarc adds SPF/DMARC for the company mail domain and has no effect when validateEmails=false. defaultPhoneCountry widens phone extraction to national-format numbers in page text (e.g. "030 1234567" with "DE"); without it only tel: links, JSON-LD and +international numbers are parsed, all returned as E.164. EXAMPLE: {"domains":["stripe.com","https://basecamp.com/about"],"maxPagesPerDomain":3} -> 2 domain rows.

Input Schema

NameRequiredDescriptionDefault
domainsYesRequired. 1+ non-empty strings: domains, website URLs or email addresses (the domain part is used); comma/semicolon/newline lists are split; deduplicated; billed once per enriched domain. Example: ["apify.com","https://stripe.com","info@basecamp.com"]
emailScopeNoOptional, one of "role" | "business" | "all", default "role" (GDPR-friendly). "role" = generic mailboxes only (info@, sales@, support@); "business" = also named addresses on the company's own domain; "all" = every address published on the pages. Example: "business"
checkSpfDmarcNoOptional boolean, default false. true adds SPF/DMARC presence and DMARC policy (none|quarantine|reject) for the company mail domain to mail_domain; requires validateEmails=true.
waitForFinishNoOptional boolean, default true. true waits up to 280 s and returns the rows; false starts the run and returns immediately with run.run_id (poll get_enrichment_results). Same price either way. Example: false
validateEmailsNoOptional boolean, default true. true runs a DNS-over-HTTPS MX lookup per email domain (no SMTP probing); false skips DNS (disposable/typo checks still run, clean emails get verdict "unknown").
maxPagesPerDomainNoOptional integer 1-15, default 5. Pages fetched per domain: homepage plus the best contact/about/imprint pages linked from it; affects time, not price. Example: 3
defaultPhoneCountryNoOptional, no default. ISO 3166-1 alpha-2 country code, exactly 2 letters, e.g. "US", "DE". When set, national-format numbers in page text are also parsed; otherwise only tel: links, JSON-LD and +international numbers.

Output Schema

NameRequiredDescriptionDefault
runNoOnly with waitForFinish=false: run_id, status, dataset_id, console_url
countYesNumber of rows in items
itemsYesDataset rows: type=domain or type=error

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed2 schema fields changedv1.1.0
    • addedInput schema / properties / waitForFinish
      Added value: +{
      +  "description": "Optional boolean, default true. true waits up to 280 s and returns the rows; false starts the run and returns immediately with run.run_id (poll get_enrichment_results). Same price either way. Example: false",
      +  "type": "boolean"
      +}
    • addedOutput schema / properties / run
      Added value: +{
      +  "additionalProperties": {},
      +  "description": "Only with waitForFinish=false: run_id, status, dataset_id, console_url",
      +  "type": "object"
      +}
  2. Changed6 schema fields changed
    • changedInput schema / properties / checkSpfDmarc / description
      Previous value: -"Default false. Add SPF/DMARC presence and DMARC policy for the company mail domain."New value: +"Optional boolean, default false. true adds SPF/DMARC presence and DMARC policy (none|quarantine|reject) for the company mail domain to mail_domain; requires validateEmails=true."
    • changedInput schema / properties / defaultPhoneCountry / description
      Previous value: -"ISO 3166-1 alpha-2 code, e.g. \"US\", \"DE\". When set, national-format numbers in page text are also parsed; otherwise only tel: links, JSON-LD and +international numbers. No default."New value: +"Optional, no default. ISO 3166-1 alpha-2 country code, exactly 2 letters, e.g. \"US\", \"DE\". When set, national-format numbers in page text are also parsed; otherwise only tel: links, JSON-LD and +international numbers."
    • changedInput schema / properties / domains / description
      Previous value: -"Required. Domains, website URLs or email addresses (the domain part is used); comma/semicolon lists are split. Example: [\"apify.com\",\"https://stripe.com\",\"info@basecamp.com\"]."New value: +"Required. 1+ non-empty strings: domains, website URLs or email addresses (the domain part is used); comma/semicolon/newline lists are split; deduplicated; billed once per enriched domain. Example: [\"apify.com\",\"https://stripe.com\",\"info@basecamp.com\"]"
    • changedInput schema / properties / emailScope / description
      Previous value: -"\"role\" (default, GDPR-friendly) = generic mailboxes only (info@, sales@, support@); \"business\" = also named addresses on the company's own domain; \"all\" = every address published on the pages."New value: +"Optional, one of \"role\" | \"business\" | \"all\", default \"role\" (GDPR-friendly). \"role\" = generic mailboxes only (info@, sales@, support@); \"business\" = also named addresses on the company's own domain; \"all\" = every address published on the pages. Example: \"business\""
    • changedInput schema / properties / maxPagesPerDomain / description
      Previous value: -"Pages fetched per domain, 1-15 (default 5): homepage plus the best contact/about/imprint pages linked from it."New value: +"Optional integer 1-15, default 5. Pages fetched per domain: homepage plus the best contact/about/imprint pages linked from it; affects time, not price. Example: 3"
    • changedInput schema / properties / validateEmails / description
      Previous value: -"Default true. DNS-over-HTTPS MX lookup plus disposable and typo checks on found emails (no SMTP probing)."New value: +"Optional boolean, default true. true runs a DNS-over-HTTPS MX lookup per email domain (no SMTP probing); false skips DNS (disposable/typo checks still run, clean emails get verdict \"unknown\")."
  3. First observedv0.1.0

TDQS

A4.9/5.0
Behavior5/5

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

Annotations already declare readOnlyHint/idempotent/openWorld, and the description goes well beyond them: robots.txt respected, per-site politeness delay, 2-10s per domain, a 280s wait cap with the waitForFinish=false escape hatch and get_enrichment_results polling path, plus an explicit price model ($0.003 per enriched domain, free for unreachable/blocked/invalid domains, ~1000-domain cap with free MAX_DOMAINS_EXCEEDED rows). That cost and long-running-operation disclosure is exactly the kind of context an agent cannot get from structured fields.

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

Conciseness4/5

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

The WHAT/WHEN/COST/RETURNS/PARAMETERS/EXAMPLE structure is strongly front-loaded with the decision-relevant content first, and the example anchors the call shape. It is long, and the PARAMETERS block restates several descriptions already present in the schema verbatim, which is the only real redundancy.

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

Completeness5/5

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

For a 7-parameter, paid, long-running open-world crawl, this covers everything an agent needs: cost exposure, runtime expectations, async polling behavior, null-vs-error row semantics, and the free preview path via normalize_domains. The presence of an output schema is complemented rather than duplicated.

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

Parameters5/5

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

Schema coverage is 100%, so the baseline would be 3, but the description adds real semantics: hostname reduction rules (leading www removed, subdomains kept as separate billable domains, email local part discarded), dedup/billing consequences, the unparseable-entry INVALID_DOMAIN behavior, the undocumented 1000-domain run cap, and tuning heuristics ('1 = homepage only, 3-5 usually finds the contact/imprint page'). It also clarifies cross-parameter dependencies (checkSpfDmarc inert when validateEmails=false) and the lawful-basis guidance for emailScope.

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

Purpose5/5

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

The WHAT block gives a specific verb (crawl homepage + contact/about/imprint pages) and resource (company domains), then enumerates the exact output fields returned. An agent knows precisely what this tool produces and can distinguish it from get_enrichment_results and normalize_domains at a glance.

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

Usage Guidelines5/5

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

It has explicit WHEN TO USE (lead/CRM lists, finding official contact email or address) and WHEN NOT TO USE (call normalize_domains to clean/preview first; not for personal emails, LinkedIn people search, or JS-only sites). The named alternative and the condition that selects it are both stated, 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.