Prospect Engine: B2B Company and Contact Enrichment
Pricing
from $10.50 / 1,000 contact verifieds
Prospect Engine: B2B Company and Contact Enrichment
Turn a domain or a hiring keyword into enriched company records and verified contacts. Resolves the real employer behind a job posting, scores ICP fit, and returns one flat Clay-ready row per entity with per-field provenance. Bring your own vendor keys.
Pricing
from $10.50 / 1,000 contact verifieds
Rating
0.0
(0)
Developer
Mamba Labs
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
4 hours ago
Last modified
Categories
Share
Find the companies hiring for a role, work out who is actually employing them, enrich the company, and find the people to talk to. One actor, one run, flat Clay-ready rows.
Bring your own keys
This actor ships no vendor keys and marks up no vendor cost. You supply your own provider keys and each vendor bills you directly. What you pay here is the Apify per-event charge and nothing else.
You can try it with no keys at all.
enrich_companiesruns on the company's own website and public sources, so the prefilled input returns a real enriched record without you configuring anything. Keys unlock job discovery and email finding.A missing key is never silently reported as an empty result. The run returns a named error saying which key is absent and how to supply it. See Vendor keys.
The thing this does that a job scraper does not: it reads the job description to decide whether the company named on the posting is the real employer or just the board that posted it. A job board, an industry association or a staffing agency posting on someone's behalf gets reported as the company by every SERP, and that attribution is wrong in a way that produces no error. This actor resolves the real employer and tells you how sure it is.
Four modes
Set mode to pick one.
enrich_companies takes domains or company names and returns one merged company record each: identity, firmographics, LinkedIn URL, social profiles, and per-field provenance. Needs no vendor key.
discover_jobs takes keywords and returns one row per posting: the real employer resolved from the job description, a signal type, an ICP score, dedupe and delta detection. Needs a SerpAPI key.
find_contacts takes domains and returns people, with an optional verified email. Needs an email finder key for the email step.
full chains all three. The originating job signal travels on every contact row, so you can write copy anchored to the posting without joining anything back together.
Inputs per mode
Everything not listed is optional and has a default. Numeric and boolean fields are supplied as strings, which is what Clay sends.
| Mode | Required | The ones worth setting |
|---|---|---|
enrich_companies | domains[] or company_names[] | score_icp, icp_preset, include_social |
discover_jobs | keywords[], serpapi_key | country, lookback_days, max_results, max_pages, remote_only, new_postings_only, previous_run_date, exclude_staffing |
find_contacts | domains[] or company_names[] | target_contacts, job_titles[], seniority[], departments[], include_email, verify_email |
full | keywords[], serpapi_key | everything above, plus min_icp_score (default 45) |
skip_cache forces a fresh enrichment. signal_taxonomy replaces the built-in
signal set. extra_exclude_names[] adds to the ICP hard-exclude list.
Five things to try
1. Enrich a company, no keys needed. The prefilled input. Returns identity, firmographics, LinkedIn, socials and provenance.
{ "mode": "enrich_companies", "domains": ["stripe.com"] }
2. Enrich a list and score it against your ICP.
{ "mode": "enrich_companies", "domains": ["stripe.com", "figma.com", "linear.app"],"score_icp": "true", "icp_preset": "proofed_editorial" }
3. Find who is hiring editors this week. Needs a SerpAPI key.
{ "mode": "discover_jobs", "keywords": ["copy editor", "proofreader"],"country": "us", "lookback_days": "7", "max_results": "25","serpapi_key": "YOUR_SERPAPI_KEY" }
4. Find contacts at a company, with verified emails. Needs an email finder key and a verifier key.
{ "mode": "find_contacts", "domains": ["stripe.com"], "target_contacts": "3","departments": ["marketing"], "seniority": ["director", "vp"],"include_email": "true", "verify_email": "true","prospeo_api_key": "YOUR_KEY", "reoon_api_key": "YOUR_KEY" }
5. The whole pipeline in one run. Discovery to enrichment to contacts, with the job signal carried onto every contact row. Bills on the chained event set, 25% below running the three modes separately.
{ "mode": "full", "keywords": ["content editor"], "country": "us","lookback_days": "7", "min_icp_score": "45", "target_contacts": "2","include_email": "true", "verify_email": "true","serpapi_key": "YOUR_SERPAPI_KEY", "prospeo_api_key": "YOUR_KEY","reoon_api_key": "YOUR_KEY" }
What comes back
One flat row shape across every mode. 64 snake_case columns, one row per entity, no nested JSON. A mode that does not populate a column leaves it null rather than dropping it, so a Clay mapping keeps working when you change mode.
The columns that matter most:
| Column | What it tells you |
|---|---|
employer_name | who is actually hiring, not who posted |
employer_confidence | 0 to 100 |
employer_resolution_method | how it was decided |
poster_is_employer | false when the posting company was the board |
needs_review | true when the attribution is not safe to trust |
signal_type | which of the seven buying signals this posting is |
icp_score and icp_tier | fit against the ICP rules, A to D or excluded |
company_phone | the company main line, E.164 when a country resolves |
company_phone_source | which node the main line came from |
field_sources | which source supplied each field, as JSON |
source_status | per-source ok, degraded, empty or skipped |
data_completeness | percentage of the contract this row filled |
Company main line. telephone is a standard property on the schema.org
organization nodes the homepage source already parses, so the switchboard
number costs no extra request, no vendor and no credit. Every JSON-LD block on
the page is scanned rather than only the block the company record resolves out
of, Organization subtypes such as LocalBusiness and ProfessionalService
count as organization nodes, and a block that is not valid JSON is skipped
rather than aborting the scan.
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. company_phone_source names the node the number came from, for
example jsonld_organization or jsonld_localbusiness, and
company_phone_raw holds the string exactly as published so a bad
normalization is diagnosable from the row alone.
The number is normalized to E.164 using the HQ country resolved from the same
address node. Where no country can be determined it is emitted exactly as
published, without a leading +, so the leading + is itself the provenance.
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.
Most sites publish no telephone, so a null company_phone is the ordinary
outcome rather than a failure. The field is excluded from data_completeness
for that reason: counting it would drag every score down against a denominator
that never applied to the scores already recorded, and this actor charges on
data_completeness > 0.
The extractor is shared with Company Firmographics
(mambalabs/company-firmographic-enricher) through
@mambalabsdev/gtm-core/phone, so one domain cannot yield two different main
lines depending on which actor you ran.
Employer resolution
The part worth understanding before you trust the output.
A posting says "National Association of Black Journalists". The description says "Southern California Edison is a proud Equal Opportunity Employer". The employer is Southern California Edison, and it is 13,700 people rather than 217, which changes who you contact and what you say.
The resolver reads the description, and the posting page when the description is truncated, looks for the patterns a company uses to name itself, and weighs them against the posting company. The equal-opportunity clause is the strongest single signal: it is legally motivated, it names the hiring entity, and a board posting on someone's behalf carries the employer's clause rather than its own.
When the evidence is not strong enough, the row is flagged needs_review instead
of being attributed. That is the point. The failure mode being fixed is confident
and wrong, so the fix has to be able to say it is unsure.
Overriding the posting company is deliberately hard. A company naming another company inside its own job description is completely normal, so a differing name is not on its own evidence of anything. An override needs either an explicit statement (on behalf of, our client, recruiting for) or a posting company that looks like a board, an association, a staffing agency or a government portal. A shared brand token or an acronym is read as the same corporate family, not a different employer.
Measured on 227 real postings pulled from Google Jobs on 2026-08-03:
| postings where the employer was overridden | 1 (0.4%) |
| postings flagged for human review | 7 (3%) |
| review flags that were correct on a hand check | 7 of 7 |
| employer confidence, median | 60 |
| employer confidence, 75th percentile | 85 |
| postings carrying a description over 500 characters | 85% |
Confidence of 100 happens, but it is rare: 4 of 227. Treat 85 as a strong result and anything at or below 60 as "the description did not corroborate the posting company".
Signal types
Every posting is classified, because a company hiring a copy editor and a company hiring four content writers want different messages.
| Type | What it means |
|---|---|
contract_editorial | already trying to buy capacity rather than hire it |
direct_editorial_hire | editorial volume exceeds current capacity |
new_editorial_owner | a budget holder just arrived with a mandate |
regulated_high_stakes | one error costs real money |
content_production_ramp | output is about to rise, editing follows |
localization_translation | multi-language output with a QA gap |
ai_generated_content | AI content at volume, unclear who checks it |
Supply signal_taxonomy to replace the set with your own. The default is
editorial because that is the vertical with a live use case, not because the
actor is limited to it.
ICP scoring
Configurable rules, not a hardcoded profile. The default preset encodes real drafted-versus-skipped decisions from a live outbound pipeline.
Hard excludes: government and public sector, student and campus media, clinical records work inside an EMR, staffing and recruitment platforms, and any posting whose employer cannot be resolved.
Scores up: contract, temporary and on-call editorial roles, because a company posting one is already shopping for the thing a managed service sells.
Size is not a disqualifier. Large employers score neutral and carry a buyer-check note telling you to target functional owners rather than the C-suite.
Vendor keys
This actor ships no keys and marks up no vendor cost. You supply your own and are billed by each vendor directly.
| Key | Needed for |
|---|---|
serpapi_key | discover_jobs and full. Note this is SerpAPI, not Serper |
serper_api_key | better contact discovery, optional |
icypeas_api_key or prospeo_api_key | finding emails |
reoon_api_key or bounceban_api_key | verifying emails |
Cost and speed
Everything runs in one container. There are no sub-actor calls, so you are not paying a second actor's list price on top of this one, and there are no container starts between stages.
Measured runtimes, 2026-08-03, one container, no sub-actor calls:
| Mode | Runtime | Work covered |
|---|---|---|
discover_jobs | 6s | 6 postings, employer resolution and scoring on each |
find_contacts | 7s | 1 company |
enrich_companies | 9s | 2 companies, full identity and firmographics and social |
full | 53s | 4 companies end to end: discovery, employer resolution, enrichment, contacts |
full at 53 seconds is over the 20 to 25 second target the architecture was
chosen against, so be straight about it: the target was per-company and this is
four companies chained, roughly 13 seconds each. The saving over a fan-out design
is real but it is a saving on container starts, not on network time, and network
time is what dominates once the containers are gone.
What it costs
Pay per event, charged on output only. You are charged for a result once it carries the thing being sold, never for making a request.
| Event | Charged when | Price |
|---|---|---|
| Company enriched, full | the merge produced data from an external source, with signals and an ICP score | $0.0800 |
| Company enriched, basic | the same, without scoring | $0.0200 |
| Contact verified | a contact is returned with a verified deliverable email | $0.0150 |
| Job posting discovered | a posting survives dedupe and resolves to an employer | $0.0100 |
Nothing is charged for a miss. A contact found without an email is free. A company that no external source could enrich is free. A posting whose employer could not be resolved is free. A cache hit is free. A notice row is free.
Subscription tiers discount every event: Bronze 10%, Silver 20%, Gold 30%.
Cheaper than assembling it yourself
The same work bought a la carte from the individual Mamba Labs actors:
| What you get | Here | As separate actors | You save |
|---|---|---|---|
| Company enriched, basic | $0.0200 | $0.0320 | 38% |
| Company enriched, full | $0.0800 | $0.1770 | 55% |
| Contact verified | $0.0150 | $0.0223 | 33% |
| Job posting discovered | $0.0100 | $0.0200 | 50% |
Running full is cheaper again. The chained path bills its own event set at
25% below the sum of the single-mode rates, because one run doing all three
stages costs less to serve than three runs doing one each. It is applied
automatically. You do not opt in and there is nothing to configure.
Free tier
Free Apify plans get 10 charged results a month. Paid plans are unlimited.
When the limit is reached the run finishes normally. It does not error. It returns the results it produced, adds a row explaining the limit was reached, and sets a run status message saying the same. Nothing is charged past the limit.
Results are cached for 7 days, so re-running the same company inside that window costs nothing.
Notes
Enable Apify Proxy with the residential group. DuckDuckGo, Brave and LinkedIn all block datacenter IPs from the platform, which lowers name-to-domain and LinkedIn resolution rates without it.
Contact discovery is the least proven part of this actor. The people cascade and the email waterfall are written against each vendor's documented API and covered by tests, but they have not been run against live vendor accounts. Treat contact output as unvalidated until you have compared fill rates on your own keys.
Built by Mamba Labs