Start from an identifier you can resolve, not a name you have to guess. Company enrichment fails at the matching step far more often than at the collection step: the data is available, but “Apex Systems” in your spreadsheet and the four different Apex entities that exist publicly are not the same problem as fetching a profile. Get resolution right and enrichment becomes mechanical. Get it wrong and every downstream field is confidently attached to the wrong company.
What does enrichment actually produce?
A useful enriched record answers three questions a name alone cannot: who is this company, how big and what kind, and what are they currently doing. Firmographics — industry, size band, headquarters, founding year — come from a company profile. Activity signals — what roles they are hiring for, in which locations, at what volume — come from job listings. The two together support far more analysis than either alone, because size tells you the denominator that hiring volume needs to be read against.
What enrichment does not produce is intent. A company posting eight engineering roles is observably hiring; whether they are expanding, backfilling attrition, or reposting stale listings is inference, and it belongs in a separate column from the observation.
How should the input list be prepared?
Before any lookup, normalize the names you already have. Strip legal suffixes, collapse punctuation and casing, and record the original string untouched in its own column. That original is the audit trail — when a match turns out wrong, the only way to diagnose it is to see what you actually searched for.
Then decide what your resolution key is. In descending order of reliability:
- A canonical profile URL, if you have one. Nothing beats an identifier the source itself issued.
- A company website domain. Strong, because domains are near-unique and most public profiles list one.
- The company name plus a location. Workable, ambiguous, and the point where you must plan for failure.
- The company name alone. Expect wrong matches and design accordingly.
If most of your list is at level three or four, the enrichment project is really a disambiguation project, and budgeting it as anything else guarantees an overrun.
What matching rules keep it honest?
Accept a match only when at least two independent attributes agree. Name similarity alone is not evidence — plenty of unrelated companies share a name across industries and countries.
The workable combinations are name plus domain, name plus location, or name plus industry where the industry came from your own list rather than from the record you are validating. That last clause matters: comparing a fetched record against a field you fetched from the same record proves nothing.
Every match should carry a match_method and a match_confidence. Not a score built from a formula nobody can explain later — just an honest label such as url_exact, domain_match, name_location, or name_only. Six months on, when an analysis looks wrong, being able to filter to name_only rows and find the error there is worth more than any amount of precision in the scoring.
Rows that fail to resolve should stay in the dataset, flagged, never silently dropped. A pipeline that quietly discards unmatched input reports a 100% match rate on whatever survived, which is the most flattering and least true number available.
Which signals come from job listings?
Job data is the highest-frequency public signal a company emits, which makes it useful and easy to over-read.
Reliable readings:
- Function mix. The distribution of roles across engineering, sales, operations, and support describes what a company is building far better than its own marketing copy does.
- Geographic footprint. Where roles are posted reveals office locations and remote policy, including markets a company has not announced.
- Seniority shape. A wave of senior individual contributors reads differently from a wave of entry-level hires.
- Technology exposure. Requirements name the stack a company actually runs, which is more current than any published case study.
Unreliable readings:
- Absolute headcount growth. Postings are not hires. Roles get cancelled, reposted across boards, and left up after being filled.
- Financial health. Hiring pauses correlate with many things, several of which are not distress.
- Salary bands as compensation truth. Posted ranges are negotiating positions shaped by disclosure law, and they vary by jurisdiction more than by company.
The discipline is to keep observations and inferences in separate columns. open_roles_30d is a count you can defend. is_expanding is a judgment, and it should be labelled as one so a reader can disagree with your threshold without re-collecting the data.
How often should it be refreshed?
Split the record by decay rate rather than refreshing everything on one schedule.
Firmographics move slowly — industry and founding year essentially never, size bands over quarters. Refreshing them weekly spends budget to confirm what you already knew.
Activity signals move constantly. Job postings appear and expire within days, so a hiring snapshot older than about two weeks is describing a company that no longer exists in that form.
Stamp every field group with its own collected_at and let consumers decide what is too old for their purpose. A single timestamp on the row forces the whole record to be treated as stale as its fastest-decaying field — or, worse, invites someone to treat a month-old hiring count as current because the firmographics were refreshed yesterday.
What does a finished record look like?
A defensible enriched row carries four layers, and keeping them visibly separate is what makes it auditable:
- Input — the original name string exactly as supplied
- Resolution — matched identifier, match method, match confidence
- Observations — firmographics and activity counts, each with a source URL and collection timestamp
- Inferences — any derived judgment, labelled, with the rule that produced it
Anyone can then trace a conclusion back to the row it came from, and back from there to the page it was read on. That trail is the difference between an enrichment dataset and a spreadsheet of plausible-looking values, and the difference only becomes visible at the moment someone challenges a number — which is exactly the moment it is too late to build it.
Continue in the directory
Turn the guide into a real sample run.
Open the current AgentX contract, check pricing and fields, then validate a narrow output.