Free company enrichment APIs: what you actually get
· 7 min read · Company enrichment
A free company enrichment API gets you a basic firmographic profile from a domain: name, description, industry, an employee range, founding year, headquarters and social links. What you don't get for free is volume, contact data, or freshness guarantees. Free options fall into three families: capped tiers of commercial databases, LLM-generated profiles, and scraping the company's own website. Each one breaks in a different way, so the useful question is which failure you can live with.
This post covers what each family returns, the catches that aren't on the pricing page, and a short script for testing any of them against domains you already know. If you are specifically replacing Clearbit, read Clearbit alternatives after the HubSpot acquisition after this one.
Family 1: free tiers of commercial databases
These are real data companies with a free plan as a funnel. The data is usually the best you'll get, and the free allowance is small on purpose.
Two examples, checked on their pricing pages at the time of writing:
- People Data Labs lists a free company data plan with up to 100 records a month. Contact data is obfuscated on that tier.
- Hunter offers a Company Enrichment API that takes a domain, and its free plan includes 50 credits a month.
What you typically get: a well-structured profile, consistent industry taxonomy, employee counts derived from some observable source, and decent coverage of mid-size and large companies.
The catches:
- The cap is the product. 50 or 100 lookups a month covers a side project or a demo. It does not cover a signup form with real traffic.
- Free tier data may be thinner. Contact fields being masked is the common case. Read the field list for the free plan, not the marketing page.
- Terms. Some providers restrict storing results, redistribution, or use inside a product you sell. Check before you write enrichment data into a CRM that a dozen people export from.
Family 2: LLM-generated profiles
A language model is asked to fill a fixed schema for a domain. Some do it from training data alone; others ground the answer in a live web search first. pingutil is in this family: by default it fetches the company's own homepage, hands that excerpt to a model with no web search, and instructs it to return nulls when it isn't sure. A search-grounded provider is supported as an alternative.
What you get: wide coverage of well-known companies, nice prose descriptions, and no per-record bill (or a very small one). On a domain like stripe.com the profile is complete and correct, because the model has seen Stripe described thousands of times.
The catches:
- Accuracy falls off with fame. A 15-person agency in Lyon is barely in any training set. A good implementation returns nulls; a bad one invents a plausible employee count.
- Freshness is bounded by the model. Without grounding, a company that raised last month or rebranded last quarter will show the old facts.
- Confidence is self-reported. A model's confidence score is a useful filter, but it is the model grading its own homework. Treat it as a ranking signal, not a probability.
We wrote up the accuracy question separately in how accurate LLM-based company enrichment is. The short version: use it for routing and segmentation, and keep a human in the loop for anything contractual.
Family 3: DIY from public sources
You can extract a surprising amount from the company's own homepage. Many sites embed schema.org Organization data as JSON-LD for search engines, which gives you a self-declared name, logo, social profiles and sometimes address and founding date.
# pip install requests beautifulsoup4
import json
import requests
from bs4 import BeautifulSoup
def homepage_org(domain: str) -> dict | None:
resp = requests.get(f"https://{domain}", timeout=10,
headers={"user-agent": "Mozilla/5.0 (enrichment-test)"})
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
for tag in soup.find_all("script", type="application/ld+json"):
try:
data = json.loads(tag.string or "")
except json.JSONDecodeError:
continue
items = data if isinstance(data, list) else data.get("@graph", [data])
for item in items:
if isinstance(item, dict) and item.get("@type") in ("Organization", "Corporation"):
return {
"name": item.get("name"),
"logo": item.get("logo"),
"same_as": item.get("sameAs"),
"founding_date": item.get("foundingDate"),
"address": item.get("address"),
}
# Fall back to Open Graph / meta tags
og = soup.find("meta", property="og:site_name")
desc = soup.find("meta", attrs={"name": "description"})
return {
"name": og["content"] if og else None,
"description": desc["content"] if desc else None,
}
print(homepage_org("example-company.com"))
What you get: data the company wrote about itself, so names and social links are usually right. It is free and uncapped, apart from your own bandwidth.
The catches: no employee count, no industry taxonomy, nothing about funding. Plenty of sites don't publish JSON-LD at all, and plenty block non-browser user agents. You also own the crawler, which means retries, timeouts, robots.txt and the occasional angry email.
Side-by-side
| Commercial free tier | LLM-generated | DIY homepage | |
|---|---|---|---|
| Monthly volume | Tens to ~100 records | Usually rate-limited, not capped | Your bandwidth |
| Employee size | Yes, often good | Estimate, weak for small firms | No |
| Industry | Consistent taxonomy | Free text, varies | No |
| Small / non-US companies | Patchy | Weakest here | Good if site has markup |
| Freshness | Provider's refresh cycle | Model cutoff, unless grounded | Live |
| Contact data | Usually masked on free | None | None |
| Main risk | Hitting the cap | Confident wrong answers | Missing fields |
A pattern that works on a budget: classify first so you never spend a lookup on gmail.com, use an LLM-based profile for breadth, and keep a paid tier or manual research for the accounts that actually matter.
Test any enrichment API with 50 domains you know
Pricing pages and sample responses always show Stripe. Your leads are not Stripe. Before committing, build a small truth set from your own CRM: 50 domains where you know the employee band and industry, with at least a third being small or non-US companies. Then measure two things separately: fill rate (how often the field comes back non-null) and accuracy (how often a non-null value is right).
This script does that against pingutil, but the scoring logic works for any provider; swap the lookup function.
# truth.csv: domain,employee_band,country
# e.g. stripe.com,5001-10000,United States
import csv
import os
import time
import requests
API = "https://pingutil.com/v1/lookup"
KEY = os.environ["PINGUTIL_KEY"]
def lookup(domain: str) -> dict:
r = requests.post(API, json={"input": domain},
headers={"Authorization": f"Bearer {KEY}"}, timeout=30)
if r.status_code == 429:
time.sleep(r.json().get("retry_after_seconds", 60))
return lookup(domain)
r.raise_for_status()
return r.json()
rows = list(csv.DictReader(open("truth.csv")))
stats = {"size": [0, 0], "country": [0, 0]} # [filled, correct]
low_conf = 0
for row in rows:
data = lookup(row["domain"])
company = data.get("company") or {}
if (company.get("confidence") or 0) < 0.5:
low_conf += 1
size = company.get("employee_size_estimate")
if size:
stats["size"][0] += 1
stats["size"][1] += size == row["employee_band"]
country = (company.get("headquarters") or {}).get("country")
if country:
stats["country"][0] += 1
stats["country"][1] += country.lower() == row["country"].lower()
time.sleep(1.1) # stay under 60 requests/min
n = len(rows)
for field, (filled, correct) in stats.items():
acc = correct / filled if filled else 0
print(f"{field:8} fill {filled}/{n} accuracy when filled {acc:.0%}")
print(f"confidence < 0.5: {low_conf}/{n}")
Two notes on reading the results. A provider with 60% fill and 95% accuracy is often more useful than one with 95% fill and 70% accuracy, because a null routes to a default queue while a wrong value routes to the wrong rep. And country strings differ between providers ("United States", "US", "USA"), so normalize before comparing or you'll undercount accuracy.
If you're running more than a few hundred lookups, cache by registrable domain. Company facts change on a scale of months; there is no reason to pay for, or wait on, the same domain twice in a week. Caching domain lookups covers key choice and TTLs.
Where pingutil fits, honestly
pingutil is free and community-run. You get classification (company, free, disposable, edu, gov, ISP) on every call and an LLM-generated company profile with a confidence score for company domains. Results are cached per registrable domain. There is no person-level or contact data, no bulk endpoint, and the per-key limit is 60 requests a minute. If enrichment fails, you get the category back with meta.enrichment_error set and company: null, so your code should treat a missing profile as normal.
That makes it a reasonable fit for signup triage, lead routing and segmentation, and a poor fit for building outbound contact lists. You can try a lookup without a key in the live demo or read the API reference for the full schema.
Before you pick one
- Decide which fields actually drive a decision. Usually it's employee band, industry and country. Everything else is nice to have.
- Build the 50-domain truth set and run every candidate against it.
- Classify before you enrich, so free and disposable domains never cost a lookup.
- Store the provider and confidence next to each enriched value.
- Re-check the free tier terms for storage and redistribution before writing into your CRM.
Questions people ask
- Is there a completely free company enrichment API?
- There are free tiers, but most commercial ones cap you at somewhere between a few dozen and a hundred records a month and hide contact data. Uncapped free options exist, usually LLM-based or community-run, and they trade coverage and freshness for price.
- What fields does a company enrichment API usually return?
- Name, description, industry, employee range, founding year, headquarters location and social profile URLs are near-universal. Revenue, funding, technographics and contacts are where free tiers either stop or get unreliable.
- How do I evaluate an enrichment API before committing?
- Run 50 to 100 domains you already know the answers for through it, including small and non-US companies. Measure how often each field is filled and how often it is right, separately.