Company Firmographic Enricher
Pricing
from $3.40 / 1,000 results
Company Firmographic Enricher
Domain to structured company firmographics: employee band, industry, HQ, founded year, revenue estimate, logo, and description from schema.org JSON-LD and meta tags. Flat JSON, Clay ready, with source provenance.
Pricing
from $3.40 / 1,000 results
Rating
0.0
(0)
Developer
Mamba Labs
Maintained by CommunityActor stats
1
Bookmarked
49
Total users
8
Monthly active users
4 days ago
Last modified
Categories
Share
🏢 What can Company Firmographic Enricher do?
Give it a company domain and it returns a structured firmographic record: employee band,
industry, HQ location, founded year, a revenue estimate, the company main line, logo and
description, parsed from the company's own schema.org/Organization JSON-LD and HTML meta tags.
Every record carries a source_signals array and a data_completeness score, so you always know
where the data came from and how much was actually found.
| 📦 What you get | ⚙️ Features and integrations |
|---|---|
| 🏢 Employee band, industry, HQ, founded year ☎️ Company main line, normalized to E.164 🔍 source_signals, exactly which sources contributed🧾 23 flat fields, always present, null when absent | 🧬 JSON-LD first, HTML meta tags as fallback 🛡️ Datacenter proxy fallback only when blocked 🧊 7 day cache, repeat lookups skip the network, not the per-row charge ⬇️ Export to JSON, CSV, Excel, HTML or XML |
Bought by Clay users, RevOps teams and outbound agencies that need enriched company records without a paid data provider dependency. It is the canonical company record the rest of the Mamba Labs fleet joins on.
🚫 This reads what a company publishes about itself, not a licensed database. No ZoomInfo, no Clearbit, no Crunchbase. It will not return data those providers hold but the company does not publish. That is the tradeoff for auditable, low-cost enrichment.
💡 Why use Company Firmographic Enricher?
| If you want | Read these fields |
|---|---|
| Company size for segmentation | employee_band, employee_count |
| Where they are | hq_location |
| How long they have existed | founded_year |
| A rough revenue band | revenue_estimate |
| A switchboard number | company_phone, company_phone_raw, company_phone_source |
| Copy for a sequence | company_name, description, logo_url |
| Whether to trust the row | data_completeness, source_signals |
🔍 Provenance on every row, so you can gate on real coverage
source_signals names exactly which sources populated the record: jsonld_organization,
meta_tag, a proxy fetch, cache_hit, and so on. data_completeness is a 0 to 100 share of the
eight core fields that were actually found.
Together they mean you never have to guess whether a null is "the company did not publish it" or
"the fetch went wrong". Gate downstream work on data_completeness >= 50 rather than assuming a
field is present.
▶️ Ready-to-run examples
| Example | What it does |
|---|---|
| Enrich Company Firmographics from a Domain | Name, industry, employee count and location from a domain |
| Company Lookup by Domain | Domain-to-company enrichment at a low per-row cost |
| Bulk Domain to Company Enrichment | Enrich a full list of domains in one batch run |
| Domain to Employee Count and Size | Headcount and size band from a domain |
| Name and Domain Enrichment with Fallback | Use both name and domain for higher match rates |
📋 What data can Company Firmographic Enricher extract?
23 fields per domain, in this table and in "More fields on every row" below. Every field is always present; absent values are null.
| Field | What it holds |
|---|---|
company_name | From JSON-LD name, meta tags, or your input fallback |
employee_band | Bucketed range, 1-10 through 10001+ |
employee_count | Raw count from JSON-LD numberOfEmployees |
industry | Best effort, often null |
hq_location | "City, Region, Country" from the JSON-LD address |
founded_year | Four digit year from foundingDate |
revenue_estimate | Heuristic band derived from employee count |
logo_url | From JSON-LD logo or og:image |
description | Company description, capped at 500 characters |
company_phone | Main line, E.164 when a country resolves |
company_phone_raw | The number exactly as published, before normalization |
company_phone_source | Which node it came from |
fetch_status | What happened on the fetch |
source_signals | Which sources populated the record |
data_completeness | 0 to 100, share of the eight core fields populated |
run_date | When the run happened |
⚠️
revenue_estimateis a heuristic, not a figure, derived from employee count using a per-employee proxy. Treat it as a rough band.industryis best effort and oftennull, becauseschema.orghas no first-class industry field. Neither should be relied on the way you would rely onemployee_count, which comes straight off the company's own published data.
📏 How often headcount and HQ are filled. Both come only from the company's own homepage structured data, and most homepages do not publish them. Measured on 2026-10-12 across 28 reachable homepages of well known software companies:
employee_count(and soemployee_bandandrevenue_estimate) was published on 3, andhq_locationon 13. The actor read every value that was published. Anullhere means the homepage does not state it, not that the read failed. A page that lists several offices without naming the headquarters returnsnullrather than a guess. For headcount on a list where most rows must be filled, use a source that estimates it, such as a data provider in Clay, and keep this actor for the companies that publish it.
🛠️ How to enrich a company from its domain
- Open the Input tab and put a bare domain in
domain, withouthttps://or a trailing slash. - Optionally set
company_nameas a fallback label for pages that do not publish one. - For a list, use
domainsinstead. It takes precedence and returns one row per domain. - Click Start.
- Check
data_completenessbefore you use the row.
🧪 Using it in Clay
Add an Apify enrichment column and map your domain column to domain. The output is flat, so every
field maps to a Clay column with no JSON unwrapping.
Gate downstream enrichment or outreach on data_completeness >= 50 so you do not spend credits
enriching rows where almost nothing was found.
☎️ How the phone number is chosen
telephone is a standard property on the organization nodes this actor already parses, so the
switchboard number costs no extra request and no extra credit.
Read in precedence order: a node's own telephone, then a nested contactPoint with a general,
main or customer service contactType ahead of sales, support and billing, then a telephone on
the address or location node, then the phone meta tags. Where two nodes disagree, the one whose
own url or @id sits on the domain being enriched wins, then the more specific node type.
The number is normalized to E.164 using the HQ country the actor already resolves. Where no country
can be determined it is emitted exactly as published, without a leading +, so the leading + is
itself the provenance: present means E.164, absent means the country was unknown and the value is
untouched. Implausible values are rejected rather than emitted: wrong digit counts, repeated and
sequential digit runs, the NANP 555-01xx fictional range and the Ofcom drama ranges.
💵 How much does it cost to enrich a company?
You are charged once per domain enriched.
| Plan | Per domain | Per 1,000 domains |
|---|---|---|
| Free | $0.004 | $4.00 |
| Bronze | $0.0038 | $3.80 |
| Silver | $0.0036 | $3.60 |
| Gold | $0.0034 | $3.40 |
There is also an Actor start event at $0.00005, charged once per run per GB of memory.
Free Apify plans get 800 results per calendar month, resetting on the 1st. Paid plans have no monthly cap.
🛑 Your spending limit is respected. Set a maximum cost per run in Apify, or max_charge_usd on the Input tab, and the run stops before it would go over, keeping every result already charged. Each row's billable_events_json lists the events that row was charged for.
💳 A domain that publishes nothing is still billed. The actor fetched the page, parsed it and reported honestly that there was nothing there, and
data_completeness: 0is a real answer.
📅 Scheduled pricing change, 2026-10-26 12:00 UTC. Results move from Apify's default per-row event to a named
resultevent at the same price on every plan, so each result row costs exactly what it costs today. A run summary row and a blocked lookup row stop being charged. Until then every row is billed as described above.
⚠️ A cached repeat lookup is served instantly but is still billed. This actor prices per result row, and Apify charges that event for every row written to the dataset, whether the answer came from the network or from the 7 day cache. Corrected 2026-08-12: an earlier version of this section said cached repeat lookups were free. They are not, and they never were. Measured on 2026-08-12, an identical three domain re-run 19 minutes after the first was charged the same three result events. The cache saves you the wait and the platform usage, not the per-row price.
⌨️ Input
Everything is on the Input tab. Provide either domain or domains.
| Field | Type | Default | What it does |
|---|---|---|---|
domain | string | stripe.com | Bare domain, no scheme and no trailing slash. |
company_name | string | none | Fallback label when the page does not publish a name. |
domains | array | none | Batch. Takes precedence over domain. One row per domain. |
companies | array | none | Web search mode. A list of {"domain": "...", "company_name": "..."}, either field may be empty. Needs anthropicApiKey. One row per company. |
anthropicApiKey | string | none | Your own Anthropic API key, stored encrypted. With it, each company is looked up by Claude Haiku with web search instead of from its homepage, billed to your Anthropic account (about 0.03 to 0.05 USD a company). |
batchSize | integer | 5 | Domains enriched concurrently per wave. Maximum 10. |
skipCache | boolean | false | Force a fresh enrichment and ignore the 7 day cache. |
failOnMostlyEmpty | boolean | false | Off by default: a domain that publishes no firmographic field returns a row with null fields, resolution_status and empty_reason, and the run ends SUCCEEDED. Set true to end the run FAILED when more than half the rows resolve no firmographic field (industry, employee count, HQ or founded year). The rows are saved either way. |
max_charge_usd | number | none | Optional. The run stops before the next result that would take this run's charges over this amount, and keeps every result already charged. Leave empty to rely only on the maximum cost per run you set in Apify. |
📤 Output
One flat row per domain, exportable as JSON, CSV, Excel, HTML or XML.
{"domain": "gitlab.com","company_name": "GitLab","employee_band": "1001-5000","employee_count": 2500,"industry": null,"hq_location": "San Francisco, CA, US","founded_year": "2011","revenue_estimate": "$250M-$1B","logo_url": "https://about.gitlab.com/images/press/logo/svg/gitlab-logo.svg","description": "GitLab is the DevSecOps platform.","company_phone": "+14155550199","company_phone_raw": "(415) 555-0199","company_phone_source": "jsonld_organization","fetch_status": null,"industry_source": null,"resolution_status": "resolved","empty_reason": null,"source_signals": ["jsonld_organization"],"data_completeness": 88,"run_date": "2026-06-19T13:13:15Z"}
🧾 More fields on every row
| Field | Type | What it holds |
|---|---|---|
industry_source | string | Which signal named the industry: jsonld_property (the site's own JSON-LD industry field), jsonld_type (a specific schema.org type such as CollegeOrUniversity), or page_text (keywords in the homepage title, description, headings and copy). Null when industry is null. |
resolution_status | string | resolved when at least one of industry, employee_count, hq_location or founded_year was found; empty when the page was read but none was; fetch_failed when the homepage could not be read; error when enrichment threw. |
empty_reason | string | Why the row did not resolve, such as homepage_unreachable:<fetch_status>, page_text_too_thin: |
row_status | string | ok, degraded, error, or notice (a notice row carries a message, not a result). Present on every row. |
error_reason | string | Why the row is degraded or an error, in plain words. Null on an ok row. Present on every row. |
billable_events_json | string | JSON list of the pricing events this row was charged for, for example [{"event":"apify-default-dataset-item","count":1}]. Lets you reconcile a run from the dataset alone. The once per run start event is not repeated on rows. |
💡 Tips
- Gate on
data_completenessrather than on any single field being non-null. - A row served from cache carries
cache_hitinsource_signals, so a null phone on a cached row is distinguishable from one on a fresh parse. - Pass
company_namewhen you have it. It costs nothing and fills the label on sites that publish no name. - Use
employee_bandfor segmentation andemployee_countwhen you need the raw number. The band is stable across sites that publish ranges rather than exact figures.
🧯 What a run that finds nothing looks like
Since 2026-10-12 one empty lookup never ends the run.
- A domain with nothing published returns its row with null fields,
resolution_status: emptyand anempty_reasonsuch asno_firmographic_signal_on_homepage. The run ends SUCCEEDED, with a warning in the status message when most rows are empty. SetfailOnMostlyEmpty: trueif you want that case to end FAILED instead. - A domain that refuses the fetch returns
resolution_status: fetch_failedand the refusal infetch_status. - Run time is budgeted. Every page request is capped by the time left in the run, less a 90
second reserve, and no new batch of domains starts when less than about 35 seconds of that budget
is left. Domains not started are listed under
skipped_for_budgetin the run'sRESOLUTION_SUMMARYrecord, so a long list on a short timeout ends cleanly with the rows it has. - A labeled run outcome, not a billed row. The run's
RESOLUTION_SUMMARYrecord (Storage, Key value store) carriesrun_outcome(found,no_results,blockedortimed_out),run_outcome_reason, the sources reached or refused, and the run ID and build. A refusal is labeledblocked, never reported as zero. This actor is priced per dataset row, and Apify charges every row written to the dataset, so the outcome is kept out of the dataset: you are never charged for a row the actor did not find beyond the per domain rows described under pricing.
⚠️ Known limits
Coverage varies by what the company publishes. Sites with rich schema.org/Organization JSON-LD
return a near-complete record; sites with only basic meta tags return name, description and logo.
data_completeness and source_signals make that transparent on every row.
revenue_estimate is a heuristic, not a figure. Treat it as a rough band, never as an
authoritative revenue number.
industry is best effort and often null. schema.org has no first-class industry field. Do not
build a segmentation on it being present.
Most sites publish no telephone. A null company_phone is the ordinary outcome, not a failure
and not a degraded run. Every other field is unaffected, and the field is deliberately excluded from
data_completeness so adding it did not reprice any historical score.
No paid data provider. This actor will not return data ZoomInfo, Clearbit or Crunchbase hold but the company does not publish.
Results are cached for 7 days. Pass skipCache: true for a live enrichment.
❓ FAQ
Why is industry null on a company that obviously has one?
Because the company did not publish it. schema.org has no first-class industry field, so this is
the common case rather than the exception. Since build 0.0.34 the page copy is not used to guess one:
a single generic word in a meta description used to decide it. Pass your own anthropicApiKey for
the web search lookup, which names the industry, a headcount band, the company type and the company's
own domain, and says whether the domain you sent belongs to the company you named.
What does the web search lookup add to a row?
official_domain (the named company's own website), domain_matches_company (whether the domain
you sent is it), company_type (direct_employer, job_board, staffing_agency or other), industry
with industry_source web_search, employee_band from the headcount band, identity_confidence
(high, medium or low), identity_evidence, web_searches, and the input_domain and
input_company_name you sent. A field the lookup could not establish is null.
What happens when a site blocks the fetch?
The actor fetches direct first, then the www host, and only falls back to a datacenter proxy when
a domain blocks, which keeps runs fast and cheap. If it is still blocked, the row comes back with
resolution_status: fetch_failed and fetch_status naming the refusal, for example
direct:http_403,proxy:challenge_cloudflare. A refusal is labeled as one; it is never reported as
a company with no data, and it is never raised as a run error.
One domain failed in my batch. Did it break the run?
No. Failures are caught per domain and the other rows are unaffected.
A JSON-LD block on the page is broken. Does that lose the whole record?
No. A block that is not valid JSON is skipped and reported in source_signals as
jsonld_malformed_block, and the scan continues through the remaining blocks.
How do I know whether a phone number was normalized?
The leading +. Present means E.164; absent means the country could not be determined and the value
is exactly as published. company_phone_raw always holds the published string.
🧩 Want other GTM data?
Mamba Labs builds custom actors for B2B go-to-market teams. The public versions of that work live here on the Store, so our users get the same tooling we build under contract.
Every actor in the suite takes a domain or a company and returns one flat row, so they stack in the same Clay table without reshaping anything.
🛠️ Need something custom built for you or your team? Tell us what you are trying to find and we will build it. Talk to Mamba Labs.
🆘 Support
Something wrong, or a company the actor reads incorrectly? Open an issue on the Issues tab with the domain and the row, and we will look at it.
ℹ️ Sourcing and legal. Every field comes from structured data the company publishes on its own site:
schema.org/OrganizationJSON-LD and HTML meta tags, read over plain HTTP. No paid data provider is called and no personal data is collected; the telephone number is a published company switchboard, not a person's line. You are responsible for how you use the output.
Built by Mamba Labs.