Industry

Enrich a company list from public pages

A company list with only names and domains is hard to act on. Much of what a sales or operations team needs is already published by the companies themselves, on contact pages, link-in-bio pages and in public registries and DNS. This post combines five lookups into a short pipeline, and is plain about what that approach can and cannot find.

By LookValid engineering. Published 2026-10-19.

What "public" means here

Every lookup in this pipeline reads something a company, a registry or a DNS zone publishes openly. None of them buys data from a broker, infers an address from a naming pattern, or guesses a title. If a company does not publish a contact detail on a page the crawl reaches, the pipeline returns nothing for it, and says so.

That makes the output narrower than a purchased database and easier to explain. Each value carries the page it came from, so a person reviewing a row can open the source and check it.

Which lookup answers which question

Field wanted, product, cost and where the value comes from
Field wantedProductCostWhere it comes from
Published emails, phones, social profileswebsite-contacts-scraper10 creditsUp to 10 pages of the company site that look like contact, about or team pages
Links and social icons on a bio pagelink-in-bio-lookup3 creditsOne public link-in-bio page, such as a Linktree page
Registration date, registrar, expirydomain-age2 creditsThe registry's own RDAP service
Link-graph authority score, 0 to 100domain-authority10 creditsCommon Crawl web graph and the Majestic Million open lists
Does the domain receive mail, and how it is set upemail-domain-check1 creditPublic DNS (MX, SPF, DMARC) plus open disposable, free-provider and role lists

Field wanted, product, cost and where the value comes from

The contacts scraper is described in detail in scrape contact details from a website via API. It returns emails, phones in E.164 form and social_profiles, and each email and phone names the page it was found on. The same lookup is available without a key as the website contact finder.

What each signal tells you

  • Contacts: addresses and numbers are read from mailto: and tel: links, visible page text and structured data. schema.org defines email, telephone, contactPoint and sameAs properties for an Organization, which is what a site can use to mark these up 1. Role addresses such as a general inbox are common; named people are not.
  • Link-in-bio: for a creator or small brand, the bio page is often the only place the links live. The lookup returns the profile name, the links with their titles, and the social icons, and answers not found when the page holds no links.
  • Domain age: the registration date comes from the registry's RDAP response, where an event with the action registration records when the object was first registered 2. A registry that publishes no RDAP service, or no date, gives you no age rather than a guess. The domain age checker runs the same lookup.
  • Domain authority: a score from two open rank lists. It is not a Moz or Semrush number, and a domain missing from both lists is not proof of low authority. The domain authority checker shows the signals behind the score.
  • Mail setup: the check reads DNS only and never opens a mail connection, so it cannot confirm that a particular mailbox exists. A null MX record is how a domain announces that it accepts no mail 3, and the check reports that as undeliverable.

A short pipeline

Run the cheap, deterministic checks first and the page crawl only for rows that pass. A domain that publishes a null MX or is not registered will not repay a crawl.

  1. Normalize each row to a bare domain.
  2. Call email-domain-check and drop rows whose signal is undeliverable.
  3. Call domain-age and domain-authority to rank what remains.
  4. Call website-contacts-scraper for the top of the list.
  5. Call link-in-bio-lookup only where the company lists a bio page.
const headers = {
  Authorization: `Bearer ${process.env.LOOKVALID_API_KEY}`,
  'Content-Type': 'application/json',
};
const call = async (product: string, body: object) => {
  const response = await fetch(`https://YOUR_API_HOST/v1/${product}`, { method: 'POST', headers, body: JSON.stringify(body) });
  return response.status === 503 ? null : response.json(); // 503: retry later, nothing charged
};
​
export async function enrich(domain: string) {
  const contacts = await call('website-contacts-scraper', { domain, max_pages: 5 });
  const age = await call('domain-age', { domain });
  const authority = await call('domain-authority', { domain });
  const mail = await call('email-domain-check', { domain });
​
  // A miss is a normal answer: found is false and the reason says why.
  return {
    domain,
    emails: contacts?.found ? contacts.data.emails : [],
    phones: contacts?.found ? contacts.data.phones : [],
    social: contacts?.found ? contacts.data.social_profiles : [],
    created_at: age?.found ? age.data.created_at : null,
    authority_score: authority?.found ? authority.data.authority_score : null,
    deliverability: mail?.found ? mail.data.deliverability_signal : null,
  };
}

One function that returns a row per domain

Billing follows success: a lookup that finds nothing is free, so a row that returns found for every product costs 23 credits and a row that finds nothing costs none. Results are cached per account, so rerunning a list does not charge twice. See how credits and success billing work.

What this cannot find

  • Anything the company does not publish. A site with a contact form and no address yields no email, and the response says no addresses, phones or profiles were found on the pages scanned.
  • People. The crawl reads pages, not directories of employees, and it does not infer an address from a name.
  • Pages behind a login, or content a page only builds with scripts after it loads.
  • Whether a mailbox exists. DNS shows how a domain handles mail, not who has an inbox.
  • Company facts such as headcount or revenue. None of the five lookups reports them.

Treat the result as a first pass that is cheap to produce and easy to audit. Where a row matters, open the source page the response names, and see the docs for each field.

Sources

  1. schema.org: Organization (email, telephone, contactPoint, sameAs), read 2026-10-19
  2. RFC 9083: JSON Responses for the Registration Data Access Protocol (RDAP), read 2026-10-19
  3. RFC 7505: A "Null MX" No Service Resource Record for Domains That Accept No Mail, read 2026-10-19