Company Firmographics Scraper
Enrich a domain or company name with exact employee count, revenue, HQ address, industry, founding year, ownership and ticker. No API key and no login needed.
How it works
- 1Open it on Apify
Hit Run on Apify — it opens the tool in the cloud, no install.
- 2Set the inputs
Adjust
companyDomains,companyNames,companyUrls(sensible defaults are pre-filled). - 3Click Run
The tool runs on Apify’s cloud and collects the data for you.
- 4Export the results
Download as JSON, CSV or Excel, or pipe straight into your app, Google Sheets, or an AI agent.
Pricing
$0.00092 per company = $0.92 per 1,000
| You are charged for | When | Price |
|---|---|---|
| Company returned | One company with its revenue, employee count, address, industry, founding year and ticker. A company that cannot be resolved returns an uncharged row instead. | $0.00092 |
Pay-per-event pricing: you are billed per result, not per subscription. Billing is handled by Apify on your own account. These are the live Apify store prices, in effect since 2026-09-20, and they are what you are actually charged.
Inputs
| Field | What it does | Type |
|---|---|---|
companyDomains | The company's own website domain, such as stripe.com or deliveroo.co.uk. This is the most reliable way to ask, because the domain is what the returned record is checked against - a profile whose website does not match yours is never charged for. Scheme and www. are stripped for you. | array |
companyNames | Company names, such as Cloudflare or Trader Joe's. Less precise than a domain: several companies share a name, so a name-only lookup is only returned when the name on the profile matches yours after its legal suffix is stripped. If you have both, put the names and the domains in the same order and each pair is treated as one company. | array |
companyUrls | Links of the form https://www.owler.com/company/shopify. Use these when you already know which profile you want, or when a name or domain lookup came back saying it was not confident. A link is taken at face value and always returned if the page exists. | array |
maxResults | A ceiling on how many companies are returned, whatever you put in the lists above. Hard limit 1,000. Keep it low while you are testing - you pay per company returned. | integer |
proxyUrls | Leave this empty. The run brings its own addresses and the cost of them is already inside the price. Fill it in only if you specifically want the traffic to leave through servers you already pay for, as http://user:pass@host:port. | array |
What you get
A structured dataset — each result includes fields like:
companyNamewebsiteindustrySectorsemployeeCountrevenueUsdfoundedYearownershipstatustickerexchangecitystatecountryowlerUrlExport every run as JSON, CSV or Excel, or send it to your app, a database, Google Sheets, or an AI agent.
Related tools in Developer & Research Tools
Other ready-to-run tools in the same category — all pay-per-use on the Apify cloud.
GitHub Scraper
Search GitHub repos and users: stars, forks, language, topics, licence, plus user bio, company and followers. No token needed. $0.90 per 1,000 rows.
Stack Overflow / Stack Exchange Scraper
Search Stack Overflow and Stack Exchange by keyword or tag. Score, answer count, views, reputation and body text. $2 per 1,000 questions.
Package Registry Scraper (npm + PyPI)
Get npm and PyPI package metadata as JSON. Version, license, author, repo, keywords and npm monthly downloads. $2 per 1,000 packages.
arXiv Scraper
Search arXiv papers by title, author, abstract or category. Get full abstracts, authors, categories, DOI, dates and PDF links. $2 per 1,000 papers.
OpenAlex Scholarly Works Scraper
Search 250M+ OpenAlex papers with no API key. Get titles, authors, venue, year, citations, DOI, OA links and full abstracts. $2.00 per 1,000 papers.
Crossref Scholarly Works Scraper
Search 150M+ papers on Crossref: DOI, title, authors, journal, publisher, date, citations and abstract. No API key. $1.00 per 1,000 works.
Where this tool sits
- Categories
- Developer & Research Tools
Company Firmographics Scraper - Revenue, Headcount and HQ from a Domain
Give it a domain, a company name or an Owler profile link. Get one row back with the legal entity name, the website, the full street address, the industry sectors, the exact employee count, the exact revenue in dollars, the founding year, the ownership, the status, and the ticker and exchange if it is listed. No account, no API key, no login, no browser.
- Employee count and revenue come back as exact integers: 7,600 staff and $13,269,000,000, not
"1,001-5,000" and "$10B+".
- The full postal address, down to the street line and the postcode.
- A company that has been bought reports
status: "Acquired"and names the parent and the month. - Every row is checked against the company you asked for. A profile that does not match is handed
over as a free note saying what it found instead, never billed as your answer.
- Run it with empty input and you get a labelled sample row, free, so you can see the shape first.
What this does not do
Read this part first, because it is where the surprises are.
- About one lookup in ten comes back without a company, and that is the honest number.
Measured on 50 real domain lookups through this Actor: 45 returned a company, 5 did not. All five misses were the same shape: a profile exists, but it is filed under a different website than the one asked for, so it was refused rather than guessed at. You are not charged for a miss. The note you get instead names the profile that was found, so you can pass its link straight back and take it deliberately. Well-known companies land more often than long-tail ones: 24 of 25 on the first batch, 21 of 25 on a deliberately obscure second batch.
- **A name on its own is a weaker question than a domain, and it is the one place a wrong company
can still get through.** Plenty of firms share a name. A name-only lookup is returned when the name on the profile matches yours once the legal suffix is stripped, but nothing then confirms it is *your* company rather than a namesake. Those rows carry matchedOn: "name" so you can see which ones rest on that. If you have the domain, use the domain: a domain lookup is only returned when the website on the profile is that exact domain, so it cannot hand you a namesake.
- Revenue is an estimate, not a filing. For public companies it tracks reported figures closely.
For private ones it is the source's own model. Treat it as a size band that happens to be written as a precise number, and do not put it in an audit.
- Blanks are the source's blanks. Small and very new companies often have a profile with almost
nothing on it. profileCompleteness is the source's own 0-100 fill rate for that record: 100 is a full profile, 25 is a stub with a name and a city. Null means not published. It does not mean zero. A company with no published revenue returns revenueUsd: null, never 0.
- No people. The underlying profile carries named executives with photographs and their LinkedIn
URLs, plus a switchboard number. None of that is emitted. This returns facts about companies.
- No funding rounds, no investors, no news, no competitors, no employee reviews. One company,
one row of firmographics.
- Country codes are not normalised. The source writes
CA,GBandUSAon the same field and
they are published exactly as found rather than converted into a standard that might be wrong. If you need ISO-2 everywhere, map USA yourself. It is the only three-letter code seen so far.
- It does not find companies for you. This is enrichment, not discovery: you bring a list, it
fills in the columns. There is no "give me every fintech in Berlin" search.
Input
{
"companyDomains": ["stripe.com", "deliveroo.co.uk", "octopus.energy"],
"maxResults": 25
}
| Field | What it does |
|---|---|
companyDomains | The company's own website domain. The most reliable way to ask, because the returned profile is checked against it. Scheme and www. are stripped for you. |
companyNames | Company names. Looser than a domain; see the note above. If you supply names and domains as two lists of the same length, they are paired up and each pair counts as one company rather than two. |
companyUrls | Links of the form https://www.owler.com/company/shopify. Taken at face value and always returned if the page exists, so this is the fix for a lookup that came back unsure. |
maxResults | Ceiling on companies returned, whatever the lists hold. Hard limit 1,000. Keep it low while testing, because you pay per company returned. |
proxyUrls | Leave empty. The run brings its own addresses. Fill it in only if you want the traffic to leave through servers you already pay for, as http://user:pass@host:port. |
Output
One row per company. A real row from a real run:
{
"ok": true,
"charged": true,
"recordType": "company",
"companyName": "Shopify, Inc.",
"shortName": "Shopify",
"website": "https://www.shopify.com/",
"domain": "shopify.com",
"description": "Shopify is a Canada-based multinational company that provides E-commerce solutions for online retailers.",
"industrySectors": ["E-commerce"],
"industryGroups": ["Software, Internet & Computer Services"],
"employeeCount": 7600,
"revenueUsd": 13269000000,
"foundedYear": 2004,
"ownership": "Public",
"status": "Independent Company",
"parentCompany": null,
"acquiredOn": null,
"ticker": "SHOP",
"exchange": "NYSE",
"street1": "151 O'Connor Street",
"street2": "Ground Floor",
"city": "Ottawa",
"state": "Ontario",
"country": "CA",
"postcode": "K2P 2L8",
"owlerUrl": "https://www.owler.com/company/shopify",
"companyId": "100740",
"profileCompleteness": 100,
"matchedOn": "domain",
"requestedName": null,
"requestedDomain": "shopify.com"
}
Field notes
companyNameis the legal entity, which is often not the brand. Airtable comes back as
Formagrid, Inc. and Instacart as Maplebear, Inc. shortName is the brand if that is what you wanted.
employeeCountandrevenueUsdare exact integers or null. Null means the source has not
published a figure. It is never written as 0.
statusis the company's standing:Independent Company, orAcquired. When it is
Acquired, parentCompany names the buyer and acquiredOn gives the month, e.g. Deliveroo PLC reports Acquired / DoorDash, Inc. / 05/2025.
tickerandexchangeare filled only for listed companies. A private company returns
null for both, and ownership says Private.
industrySectorsis the specific sector, such asE-commerce.industryGroupsis the broad
bucket above it, such as Software, Internet & Computer Services. Thin profiles often have the group and not the sector.
matchedOnsays which evidence returned this row:domainmeans the website on the profile
is exactly the domain you gave, and is the strongest; name is a name-only lookup, where the name is the only evidence there was; url means you supplied the link yourself. If you only want the strongest, filter on matchedOn == "domain".
profileCompletenessis the source's own 0-100 fill rate. Sort by it descending and the thin
records fall to the bottom.
requestedNameandrequestedDomainecho what you asked for, so you can join the output
back onto your input list without matching on names.
What it costs
$0.92 per 1,000 companies returned, plus a $0.0002 start fee per run. Flat on every plan, with no volume tiers, no minimum, no subscription.
You are charged only for companies that come back with data. About one lookup in ten cannot be resolved to a profile, and those return an uncharged row telling you so. On a list of 1,000 domains you would expect roughly 900 rows and a bill near $0.83, not $0.92.
Nothing else bills. A run that reaches nothing is free past the start fee, a domain that resolves to no profile is free, and a row dropped because the profile's website did not match the domain you asked for is free. That last one is deliberate, and it is why a lookup occasionally returns nothing rather than returning a company with a similar name.
How it works
It reads the company's public profile page and pulls the structured record the page is built from, rather than scraping rendered text. That is why the numbers are exact integers instead of parsed strings, and why a field that is genuinely empty comes back null rather than as a guess.
The site refuses a share of requests outright. Six different connection profiles were measured against it from the cloud, and they were not close: the one this Actor reaches for first answered 42 out of 42, while the profile most scrapers reach for by habit managed 11 out of 45 in the same job against the same addresses. When a request is refused the run changes both its connection profile and its outbound address and asks again, up to six times, and a refusal that survives all six is reported as a free note rather than billed.
Turning a domain into the right profile is the other half. The address is derived from the domain and the record that comes back is then checked against what you asked for, because a plausible-looking address can be a real page belonging to a different company with a similar name. That check is the reason a small number of lookups return nothing: refusing is the right answer when the alternative is billing you for somebody else's headcount.
What people use it for
- Filling in the size, revenue and location columns on a CRM export that arrived as nothing but
company names and domains.
- Scoring inbound sign-ups by company size before a human looks at them.
- Territory and account planning, where a real headcount matters more than a band.
- Checking whether a supplier or counterparty still exists independently, since an acquired company
reports its parent.
- Building a market map from a list of domains you already have.
Reading the output
Every run writes three kinds of row and they are easy to tell apart:
- Real rows carry
"charged": trueand"recordType": "company". One billed event each. - The sample row carries
"_sample": true,"charged": falseand"recordType": "sample".
There is exactly one, it only appears when the input had no company in it, and it exists so you can see the shape before you spend anything.
- Notes carry
"_diagnostic": true,"charged": falseand anerrorCodeworth switching on:
NO_RESULTS when a profile was found but did not match the company you asked for (the message names what it found), NOT_FOUND when no profile exists at all, BLOCKED when the source refused every attempt, BAD_INPUT when an entry could not be turned into a lookup, TIME_BUDGET when the run ran out of time before reaching an entry.
The difference between NO_RESULTS and BLOCKED is the one that matters: the first means the company genuinely could not be confirmed, the second means nobody got to look and a re-run will usually succeed. Neither is charged.
If you only want data, filter on charged == true. The count of those rows is the number of events you were billed for, so the dataset is its own invoice.
Limitations
- Coverage is good for established businesses and thin for very small, very new or privately held
local companies. A profile may exist with only a name, a city and a founding year.
- Revenue and employee figures are the source's estimates. They are refreshed periodically, not
live, and a company that grew this quarter may still read last quarter's headcount.
- A company with no profile returns an uncharged note. There is no fallback to another source.
- Name-only lookups are refused unless the name matches exactly after the legal suffix is stripped,
so a common or abbreviated name may come back unmatched even though the company is covered.
- The source writes country codes inconsistently (
CA,GB,USA) and they are published as found. - Executives, phone numbers, funding rounds, investors, acquisitions made, competitor lists and news
are all present on the source record and all deliberately not emitted.
- The hard ceiling is 1,000 companies per run. Split bigger lists across runs.
- A share of requests is refused by the source at random. The run retries across several connection
profiles and addresses, and reports the ones that never got through without charging for them.
Questions
Do I need an account or an API key for anything? No. There is no login, no cookie and no key anywhere in this Actor, and you do not need an account with the source.
Why is revenue null on a company I know makes money? Because the source has not published a figure for it, which is common for private companies. Null means "not published". It is never written as 0, so you can tell a genuine blank from a genuine zero.
I asked for a domain and got a note saying it was not confident. What now? That means a profile was found whose website is a different domain from the one you gave. The note names it. If it is the company you meant, put its owler.com/company/... link in companyUrls and it will be returned. If it is not, you have been saved from a wrong row.
Can I look up a company by name only? Yes, but it is the weakest of the three inputs and it is deliberately strict. Supplying the name and the domain together as two lists of the same length is better than either alone.
Is the employee count exact or a band? An exact integer, when the source publishes one. That is the main reason to use this rather than a profile scraper that returns "1,001-5,000 employees".
What happens on a company that has been acquired? status reads Acquired, parentCompany names the buyer and acquiredOn gives the month. The rest of the row still describes the company you asked about, not the parent.
Will the run fail if the source is down? No. You get uncharged notes saying what could not be reached, and the run still finishes as succeeded. A failed run would still bill the start fee, which would mean paying to be told something was broken.
Can I run this on a schedule? Yes. Nothing is held between runs, and requestedDomain gives you a stable key to join today's rows onto yesterday's.