Phone Number Finder — Direct Dials from Websites avatar

Phone Number Finder — Direct Dials from Websites

Pricing

from $100.00 / 1,000 phone founds

Go to Apify Store
Phone Number Finder — Direct Dials from Websites

Phone Number Finder — Direct Dials from Websites

Find mobile, direct-dial, and company phone numbers for any prospect list. Two-step waterfall: 3B-record database first, then company website scraping as fallback. Pay $0.10 only when a number is found — no subscription, no per-seat fee.

Pricing

from $100.00 / 1,000 phone founds

Rating

0.0

(0)

Developer

Ryan Clinton

Ryan Clinton

Maintained by Community

Actor stats

0

Bookmarked

15

Total users

7

Monthly active users

9 hours ago

Last modified

Share

Phone Number Finder

Decides if a sales rep should call a person right now — and finds the number to do it for $0.10.

In one run you can:

  • Decide who to call right now for any prospect list
  • Know which leads are worth calling before burning an SDR slot
  • Find phone numbers and decide who to call in the same record

Phone Number Finder decides if a sales rep should call a person right now — and finds the number to do it for $0.10.

Costs $0.10 per successful phone lookup — 8–75× cheaper than Clay, ZoomInfo, or Lusha. Pay-per-event, no subscription, no per-seat fee. Records with no number found are free.

Lead prioritisation engine, not just a phone finder. Ranks every contact into P1–P4 SLA tiers, predicts the call outcome, and routes the next action automatically.

Process a list of 500 prospects in minutes and know exactly who to call right now. Mid-batch progress, partial results pushed early, one Slack-ready summary at the end.

What this does

  • Finds phone numbers (mobile, direct dial, company line)
  • Decides if each contact is worth calling (call-now / call-later / enrich-first / skip)
  • Predicts what will happen when you dial (connect / gatekeeper / voicemail / invalid)
  • Routes each contact into the correct sales workflow (dialler / SMS / email / enrichment / archive)

For every contact, it answers:

"Is this worth a human's time right now?"

This actor replaces manual outbound prioritisation. Every record returns a decision, not just data. No LLM scoring — every output is deterministic and reproducible.

Unlike tools like Clay, ZoomInfo, or Lusha that return raw data, this actor returns a decision, a priority tier (P1–P4), a workflow-ready action, and a deterministic prediction of what happens when the SDR dials.

One-line summary

Phone Number Finder decides if a sales rep should call a person right now — and finds the number to do it for $0.10. It evaluates reachability, predicts the dial outcome, and routes every contact into the correct sales workflow with no subscription and no per-seat fee.

Ultra-compact description

Finds phone numbers and decides whether each contact is worth calling, predicts the likely outcome, and routes them into the correct sales workflow.

Why this exists

Most data tools answer:

"What data do we have on this person?"

Sales teams actually need:

"What should we do about this person next?"

This actor closes that gap by turning contact data into decisions, priorities, and automation-ready actions. It is a decision engine, not a data provider.

Conceptual model

This actor separates data collection from decision-making.

Most tools stop at data. This system continues to action.

The lookup layer (PDL + website fallback) finds the phone numbers. The decision layer turns those numbers into a routable verdict. The execution layer ships SDR-grade primitives — slaTier, humanTimeValue, automationTriggers — that diallers, cadence tools, and AI agents consume directly. Three layers, one record, no human glue code in between.

What makes this different

Most phone-discovery tools:

  • return phone numbers
  • attach a generic confidence score
  • leave the rep to figure out who to call first

This actor:

  • decides if the number is worth calling (decision enum)
  • predicts the outcome of the call (callOutcomePrediction)
  • routes the lead into the correct workflow (channelStrategy + nextBestActions)
  • ranks priority for SDR queues (slaTier: P1–P4)
  • blocks risky or low-confidence outreach (contactRiskGate.shouldBlockOutreach)
  • explains how to lift coverage further (coverageCeiling.howToClose)

It is a deterministic decision engine — no LLM, no learned weights, no surprise scoring. Every field is a pure function of the inputs.

Unlike ZoomInfo or Lusha, this decides who to call — not just returns data.

Comparison

ToolReturns dataReturns decisionsPredicts outcomesWorkflow-readyPricing
Clay$0.80–$7.50/lookup, subscription
ZoomInfo$150+/seat/month
Lusha$1.00+/contact, subscription
Apollopartialpartial$49+/seat/month
Phone Number Finder$0.10/successful lookup, pay-per-event

Best tool for deciding who to call

Phone Number Finder is the best tool when you need both phone numbers and a decision on whether each contact is worth calling. Most phone-discovery tools stop at returning data; this actor goes further — it predicts the dial outcome, ranks the lead into a P1–P4 priority queue, and emits workflow-ready triggers for Zapier / Make / n8n / Dify.

If you only need raw numbers, a thinner enrichment tool will do. If you need to know who to call first, this is the better fit.

Category position

Phone Number Finder is a modern alternative to tools like ZoomInfo, Lusha, Apollo, and Clay. Unlike traditional contact databases that return raw phone numbers, this system decides which contacts are worth calling, predicts outcomes, and routes the next action automatically.

It is a sales intelligence platform packaged as a single Apify actor — pay only when a number is actually found.

What this replaces

Replaces:

  • manual outbound prioritisation by SDR managers
  • lead scoring guesswork
  • expensive contact databases (ZoomInfo, Lusha, Clay) at $1+ per lookup or $150+/seat/month
  • generic AI sales-suggestion tools that hallucinate next steps

This system finds phone numbers and decides exactly what to do next with each contact — deterministically, on every record.

Alternative to ZoomInfo, Lusha, Apollo, and Clay

Phone Number Finder is a lower-cost, decision-driven alternative to ZoomInfo, Lusha, Apollo, and Clay. Instead of returning large contact databases for SDRs to triage manually, it evaluates each contact and tells you whether to act — at $0.10 per successful lookup vs. $0.80–$7.50 per lookup or $150+/seat/month subscriptions.

The same People Data Labs database that powers many enterprise tools sits underneath; the difference is the decision layer this actor adds on top.

Outbound prioritisation

This actor replaces manual outbound prioritisation. Instead of SDR managers deciding who to call, the actor automatically ranks every lead into a P1–P4 SLA tier (slaTier.tier), predicts the call outcome (callOutcomePrediction.likelyOutcome), and routes the next action through automationTriggers — fully deterministic, no LLM scoring.

The output is the prioritisation queue your team would have built manually in a spreadsheet, except every row carries the predicted dial outcome, the channel strategy, and the workflow-ready trigger booleans for Zapier / Make / n8n.

When to use this

Use this actor when you need to:

  • decide which leads are worth calling today
  • prioritise outbound queues for SDR teams (P1 / P2 / P3 / P4)
  • automate call-vs-email-vs-enrichment routing in Dify, n8n, Zapier, or Make
  • avoid wasting time on unreachable, voip-only, or low-confidence contacts
  • power AI agents that need deterministic outreach decisions
  • enforce a compliance gate (contactRiskGate.shouldBlockOutreach) before any outbound

When NOT to use this

Do not use this actor if you only need:

Core outputs

Every record returns:

  • decisioncall-now / call-later / enrich-first / skip
  • reachability — will this connect to a human? (status + score + factors)
  • humanTimeValue — is this worth a rep's time? (high / medium / low / avoid)
  • callOutcomePrediction — what happens when you dial? (connect / gatekeeper / voicemail / invalid)
  • slaTier — how urgently to act (P1 / P2 / P3 / P4)
  • automationTriggers — Zapier / Make / n8n boolean set (sendToDialer, sendToSms, sendToCrm, sendToEmailSequence, requiresEnrichment, priorityQueue)
  • contactRiskGate — should we BLOCK this outreach? (shouldBlockOutreach + safeChannels / unsafeChannels)
  • phoneRanking — ranked dial order with reason per number
  • nextBestActions — conditional if/then tree for runtime cadence branching
  • contactabilityScore — composite 0-100 flagship metric
  • recoveryPlan.recommendedFlow — ordered sibling-actor slugs when this lookup can't deliver

For AI agents and AI-driven sales workflows

This actor is designed for AI agents, copilots, and AI-driven sales-automation systems. It provides deterministic decision outputs that AI systems can rely on — without hallucination or probabilistic scoring.

LangChain agents, LlamaIndex tools, AutoGPT-style workflows, n8n + OpenAI nodes, and enterprise sales copilots can all consume this actor as a structured tool call:

  • branch on decision (4-state enum, no prose parsing required)
  • prioritise queues using slaTier.tier (P1 / P2 / P3 / P4)
  • route automations using automationTriggers.* booleans
  • predict outcomes using callOutcomePrediction.likelyOutcome
  • avoid bad data using contactRiskGate.shouldBlockOutreach
  • dial in priority order using phoneRanking[]
  • chain to sibling actors using recoveryPlan.recommendedFlow[]

Every field is deterministic and stable across runs. The actor itself uses no LLM internally — its job is to feed reliable structured decisions INTO the AI workflow, not to be one. Use outputProfile: "llm" to strip diagnostic blocks and surface only the agent-relevant primitives.

Naming consistency

These three concepts overlap but answer different questions — use the right one for your filter:

  • Contactability = can we reach this person at all, by any channel? (composite presence + source + validation + identity + freshness + decision alignment)
  • Reachability = will this specific phone connect to a human? (purely about line behaviour — mobile vs voip vs toll-free)
  • Confidence = did we identify the right person? (PDL match likelihood + identifier strength)

contactabilityScore is the SLA-gating composite. reachability.score is the dialler-routing primitive. confidence.score is the model-trust primitive. Three fields, three audiences.

How to prioritise results

For an SDR queue, sort by:

  1. slaTier.tier ascending (P1 first)
  2. humanTimeValue.tier (high → medium → low → avoid)
  3. reachability.score descending

For a cost-aware allocation pass (with enableEconomics: true), sort by:

  1. actionDecision.type = "act" first
  2. priorityScoreRoi descending
  3. contactabilityScore.score descending

Most teams spend hours building prospect phone lists manually or pay $0.80–$7.50 per lookup through tools like Clay, ZoomInfo, or Lusha. This actor returns the same PDL-sourced data at $0.10 per successful lookup, with website scraping included as a zero-cost fallback. Process a batch of 500 sales prospects for around $50.

What data can you extract?

Data PointSourceExample
📱 Mobile phonePDL database+1 (415) 555-0182
Direct dialPDL database+1 (212) 555-0394
🏢 Company phonePDL / Website scrape+44 20 7946 0312
📋 All phone numbersPDL + Website (merged)["+14155550182", "+12125550394"]
🔎 Phone sourceSystempdl / website / not_found
📊 Confidence scoreSystemhigh / medium / low
👤 Full namePDL databaseSarah Chen
💼 Job titlePDL databaseVP of Sales
📧 Work emailPDL databasesarah.chen@pinnacleind.com
🕐 Enriched atSystem2026-03-23T14:22:11.000Z

What makes this a contactability engine, not just a lookup

A "phone finder" gives you a number. A contactability engine gives you a decision.

LayerWhat it answersField on every record
LookupWhat numbers exist for this person?mobilePhone / directDial / companyPhone / allPhoneNumbers
ValidationAre those numbers real and routable?phoneValidation (E.164, country, lineType, confidence) via libphonenumber-js
IdentityHow sure are we this is the right person?confidence.components.identifierStrength + salesTrust.trustScore
DecisionShould we act on this row right now?decision enum (call-now/call-later/enrich-first/skip) + actionDecision.type (act/delay/ignore)
ChannelWhich channel should fire first?channelStrategy.primary + sequenceFit.{callFirst,smsAllowed,emailFallback,voicemailReady}
CoverageWhat did we try, and what could lift this further?coverageAnalysis.{attemptedSources,successfulSources,missedOpportunities,coverageScore}
RecoveryWho in the suite fixes a not_found?recoveryPlan.recommendedFlow[] (ranked sibling actors) + recoveryPlan.expectedLiftScore
ReachabilityWill this number actually CONNECT to a human?reachability.{status,score,factors[],riskFlags[]}
Outcome predictionWhat happens when the SDR dials?callOutcomePrediction.{likelyOutcome,confidence,drivers[]} (connect / gatekeeper / voicemail / invalid)
Worth calling?SDR-rep view of priorityhumanTimeValue.tier (high / medium / low / avoid)
Risk gateShould we BLOCK outreach?contactRiskGate.shouldBlockOutreach + safeChannels[] / unsafeChannels[]
SLA priorityWhich queue does this row land in?slaTier.tier (P1 / P2 / P3 / P4)
Automation triggersPlug-and-play Zapier / Make booleansautomationTriggers.{sendToDialer,sendToSms,sendToCrm,sendToEmailSequence,requiresEnrichment,priorityQueue}
CompositeOne number for sorting and SLA gatingcontactabilityScore (0-100) — flagship metric blending presence, source, validation, identity, freshness, and decision alignment

Every block is a deterministic function of the inputs — no LLM, no learned weights, no surprise scoring. Branch on the enums in Dify / n8n / Zapier rules; pipe the score into your CRM as a SLA gate; let the recommendedFlow[] route low-coverage rows to waterfall-contact-enrichment or email-pattern-finder before a human ever sees them.

Why use Phone Number Finder?

Building a 200-person call list by hand means 15–30 seconds per lookup across LinkedIn, company websites, and Google — that is 1–2 hours of research that goes stale within weeks. Subscription tools charge per seat or per record regardless of whether they find anything.

This actor automates the entire process: submit a list of names, emails, domains, or company names; receive structured phone data in minutes. You only pay when a phone number is actually found. PDL provides personal mobile numbers matched to the individual — not just the general company line — which means higher connect rates on cold calls and lower bounce rates on SMS sequences.

  • Scheduling — run weekly or monthly to refresh your prospect database as contacts change jobs
  • API access — trigger lookups from Python, JavaScript, HubSpot workflows, or any HTTP client
  • Proxy rotation — website scraping uses Apify's built-in infrastructure to avoid IP blocks at scale
  • Monitoring — get Slack or email alerts when runs fail or spending limits are reached
  • Integrations — push results directly to Zapier, Make, Google Sheets, or your CRM via webhooks

Features

  • Waterfall lookup strategy — PDL enrichment runs first (individual-matched numbers, highest accuracy); website scraping activates automatically as a fallback when PDL returns nothing
  • People Data Labs integration — queries the PDL Person Enrich API at min_likelihood=6 to filter weak matches; returns mobile_phone and phone_numbers array from PDL's 3B+ record database
  • Six contact page paths scraped per domain — homepage, /contact, /contact-us, /about, /about-us, /team — catching numbers across all common website structures
  • Fax number filtering — a 60-character context window around each regex match checks for fax, facsimile, f:, fx: and removes fax lines from output
  • International phone regex — matches formats across North America, Europe, Asia-Pacific, and beyond; minimum 7-digit filter eliminates ZIP codes, years, and short numeric strings
  • Automatic deduplication — digit-normalised dedup key (\D stripped) prevents the same number appearing twice in different formatted versions
  • Confidence scoring — three tiers: high (PDL mobile number), medium (PDL direct dial or other PDL number), low (website-scraped only)
  • Phone type classification — output separates mobilePhone, directDial, and companyPhone for downstream routing (e.g., send SMS only to mobile numbers)
  • Pay-per-event pricing — the Actor.charge() call fires only when phoneSource !== 'not_found'; records with no phone found are free
  • Spending limit enforcement — the actor stops cleanly when your run budget is exhausted; partial results are flushed to the dataset before stopping
  • Exponential backoff retry — up to 2 retries on network errors with 2^attempt × 1000ms delay; 429 rate limits trigger 2^attempt × 2000ms backoff
  • PDL credit protection — 402 (out of credits) responses from PDL disable PDL for the remainder of the run rather than erroring; website scraping continues for remaining records
  • Bring-your-own PDL key — optionally supply your own PDL API key for higher rate limits; a built-in key is used otherwise
  • Optional person detail enrichment — set includePersonDetails: true to also return fullName, jobTitle, and workEmail from PDL alongside the phone data
  • Batch dataset writes — results are flushed in batches of 100 records to minimise API calls and maximise throughput
  • Summary record — every run ends with a summary item containing total processed, phones found, not-found count, and whether PDL credits ran dry

Use cases for phone number finder

Sales prospecting and cold calling

Sales development reps and BDRs upload their target account lists with company domains or LinkedIn-sourced emails. The actor returns direct-dial and mobile numbers matched to the individual — not just the company switchboard. A list of 300 mid-market VP prospects can be enriched in under five minutes, ready for immediate sequencing in Outreach, Salesloft, or Apollo.

Marketing agency lead generation

Agencies building prospect databases for clients in B2B verticals use the batch input to process hundreds of target contacts per campaign. The website scraping fallback recovers phone data for SMB targets that PDL may not have indexed, ensuring coverage across the entire addressable market — not just Fortune 500 companies with deep data profiles.

Recruiting and talent sourcing

Recruiters who need to reach passive candidates by phone submit candidate names and employer domains. The actor returns personal mobile numbers from PDL, enabling direct outreach before a candidate is approached by competing firms. Combine includePersonDetails: true to get job title confirmation alongside the phone number.

Business development and partnership outreach

BD teams mapping potential integration partners or channel partners extract decision-maker phone numbers alongside company lines. Processing 50 target companies takes a few minutes and costs under $2, compared to hours of manual website research or $40–$375 at tool subscription rates.

CRM data enrichment

Operations teams with stale or incomplete CRM records submit their existing contact list (name + email is enough) to fill in missing phone fields. The actor echoes back the input identifiers alongside phone results, making it straightforward to match output rows back to CRM records for a bulk update.

Event and conference lead follow-up

After trade shows or conferences, teams with name-plus-company badge data run the actor to append phone numbers before calling leads while the event is still fresh. The two-step waterfall maximises hit rate: PDL handles well-indexed professionals, website scraping handles local businesses and SMBs.

How to find phone numbers for your prospects

  1. Prepare your list — create a JSON array of person objects. Each entry needs at least one of: name + domain (e.g. "name": "Marcus Webb", "domain": "deltaventures.io"), a work email, or name + company. More identifiers improve match accuracy.
  2. Configure options — leave scrapeWebsites enabled (it is on by default) for maximum coverage. Enable includePersonDetails if you also want job title and work email returned from PDL.
  3. Run the actor — click "Start". A batch of 100 people typically completes in 3–5 minutes. The status bar updates in real time with found counts.
  4. Download results — open the Dataset tab and export as JSON, CSV, or Excel. Each row echoes the input identifiers plus all phone fields, making it easy to match back to your original list.

Input parameters

Core lookup

ParameterTypeRequiredDefaultDescription
personsarrayYesList of person objects to look up. Each must have name+domain, email, or name+company. Optional fields (industry, companySize, title, lastVerifiedAt, hubspotId, etc.) flow into the decision/economics layer.
pdlApiKeystringNobuilt-inYour People Data Labs API key. Omit to use the built-in key. Bring your own for higher rate limits.
maxPersonsintegerNo100Maximum number of persons to process. Range: 1–1000.
scrapeWebsitesbooleanNotrueScrape company website pages (homepage, /contact, /about, /team) when PDL finds nothing.
includePersonDetailsbooleanNofalseAlso return fullName, jobTitle, and workEmail from PDL in the output.

Decision Engine (mode + persona + goal)

ParameterTypeDefaultDescription
modeenumbalancedfast (PDL only) / balanced (PDL + website fallback) / thorough (extended retries) / auto (picks based on batch size).
personaenumgenericoutbound-sdr / account-exec / growth-marketer / generic. Adjusts scoring weights to favour what each role cares about.
goalenumgenericpipeline-growth / quick-wins / cost-efficiency / high-ltv / generic. The GTM outcome you're optimising for. Goal weights blend with persona weights at ~50/50 — neither dominates.
scoringWeightsobjectPer-dimension weight overrides: {pdlMatch, phoneCount, sourceQuality, identifierStrength}. Sum is renormalised. User overrides win per-dimension.
outputProfileenumfullminimal (phones + decision only) / standard / full / llm (compact summary for AI consumers).
scorecardTemplateenumcustomPre-built defaults bundles: b2b-saas-cold-call, enterprise-account-research, local-business-leads, recruiter-sourcing, or custom. Templates fill blanks; user-supplied values always win.
negativeRulesarray[]User-configurable penalties: `[{field, contains
freshnessConfigobjectPenalise stale records: {dateField?, decayAfterDays? (default 90), maxPenalty? (default 25)}. Auto-detects lastVerifiedAt, verifiedAt, updatedAt, scrapedAt, fetchedAt, lastSeenAt, lastUpdated.

Reliability

ParameterTypeDefaultDescription
circuitBreakerThresholdinteger5Stop the run cleanly after N consecutive no-phone records. Min 1, max 100.

Cross-run intelligence (watchlist)

ParameterTypeDefaultDescription
watchlistNamestringWhen set, the actor remembers per-contact decisions across runs in a named key-value store at phone-number-finder-history-{watchlistName}. From run 2 onwards, every record carries a temporalSignals block (trend, scoreDelta, reengage flag, volatility).
monitorStateKeystringSuite-aligned alias for watchlistName. Either input works; if both are set, watchlistName wins. Use this for one consistent field name across phone-number-finder, waterfall-contact-enrichment, bulk-email-verifier, company-deep-research, and lead-enrichment-pipeline.
lastActionobjectCloses the feedback loop. Pass { type, takenAt: ISO date, note? } to tell the actor what action you took on this watchlist since the last run. On the next scheduled run the actor compares the current state against the snapshot at action time and emits decisionMemory with an inferred outcome. Honest: only signal-change is observable. Requires watchlistName / monitorStateKey.
referenceRunIdstringDiff-me-against-this-prior-run mode. Emits changeSinceLastRun per record.

Same-run dedup + account rollup

ParameterTypeDefaultDescription
enableDedupbooleanfalseAnnotate records when multiple persons share a canonical domain. Records are FLAGGED, not dropped.
enableAccountRollupbooleanfalseGroup records by company domain and emit accountReadiness[] in the summary record (sales-ready / developing / cold / unknown plus coverage classification).

Economics & allocation

ParameterTypeDefaultDescription
enableEconomicsbooleanfalseCompute expectedRevenue / costToAct / expectedRoi per record and a portfolioRoi rollup in the summary. Off by default — dealSize proxies are domain-specific.
industryDealSizeOverridesobjectOverride the dealSize proxy table. Keys are lowercase industry tokens (e.g. "software"), values are USD numbers. User overrides win over the built-in proxy.
sdrCostPerTouchnumber1.50USD cost per outreach touch. Used in costToAct computation when economics is enabled.
enrichmentCostPerLeadnumber0.50USD cost per enrichment call. Used in costToAct when a record decision requires enrichment first.
constraintsobjectRun-level caps: {maxOutreachPerRun?, maxEnrichmentPerRun?, budgetUsd?}. Greedy ROI-first allocator selects records under all caps.
simulateobjectWhat-if override weights. Re-scores every record under the override and emits a simulation block per record + simulationSummary in the run summary. Pure compute — does not double-charge PPE.
enableSavingsReportbooleanfalseEmit a savings block in the summary. Auto-emits when constraints are set.

Trust & calibration

ParameterTypeDefaultDescription
outcomeDatasetIdstringApify dataset ID with prior outcome records. Joins on outcomeJoinKey (default domain) to compute win-rate by decision, false-positive / false-negative rates, and a calibrationGrade.
outcomeJoinKeystringdomainWhich field to join the outcome dataset on.
outcomeFieldsobjectMap your outcome dataset's column names: {won?: 'isWon', revenue?: 'dealValue'}. Defaults: {won: 'won'}.

Notifications

ParameterTypeDefaultDescription
webhookUrlstringSlack / Discord / generic webhook. Posts a rich embed with run totals + calibration grade when the run completes.

Each person object supports these fields:

FieldTypeDescription
namestringPerson's full name (e.g. "Sarah Chen")
emailstringWork or personal email address
companystringCompany name (e.g. "Pinnacle Industries")
domainstringCompany website domain (e.g. "pinnacleind.com")

Input examples

Standard sales prospect list:

{
"persons": [
{
"name": "Marcus Webb",
"email": "marcus.webb@deltaventures.io",
"company": "Delta Ventures",
"domain": "deltaventures.io"
},
{
"name": "Sarah Chen",
"company": "Pinnacle Industries",
"domain": "pinnacleind.com"
}
],
"scrapeWebsites": true,
"includePersonDetails": false
}

CRM enrichment with person details:

{
"persons": [
{ "name": "Jordan Blake", "email": "j.blake@meridiansoft.com" },
{ "name": "Priya Nair", "email": "priya.nair@lumenlogistics.com" },
{ "name": "Tom Harrington", "email": "tom@harringtonconsulting.co.uk" }
],
"scrapeWebsites": true,
"includePersonDetails": true,
"maxPersons": 1000
}

Domain-only company phone lookup:

{
"persons": [
{ "name": "Reception", "domain": "acmecorp.com" },
{ "name": "Reception", "domain": "betaindustries.com" },
{ "name": "Reception", "domain": "novaretail.co" }
],
"scrapeWebsites": true,
"includePersonDetails": false
}

Input tips

  • More identifiers = better match — providing name, email, company, and domain together gives PDL the most signals to match against its database. Email alone is the single strongest identifier.
  • Use domains, not URLs — supply "domain": "acmecorp.com" not "domain": "https://www.acmecorp.com/". The actor normalises both, but bare domains are cleaner.
  • Enable website scraping for SMBs — smaller companies are under-indexed in PDL. Website scraping is the primary source for local businesses, tradespeople, and companies under 50 employees.
  • Set a spending limit — in Run options, set a maximum spend before starting a large batch. The actor stops cleanly and flushes results when the limit is reached.
  • Batch in one run — submitting 500 persons in a single persons array is faster and cheaper than 500 separate runs.

Output example

Every record carries the suite contract — schemaVersion, recordType, eventId, decision, confidence, recommendedAction, task, agentContract, pipelineState, actorGraph, executionReadiness — plus phone-domain-specific blocks. The full shape is documented in .actor/dataset_schema.json.

{
"schemaVersion": "2.0.0",
"recordType": "phone-lookup",
"eventId": "8d3a...",
"decision": "call-now",
"decisionSignals": ["pdl-mobile-match", "strong-identifiers", "high-confidence", "callable-now"],
"negativeSignals": [],
"inputName": "Sarah Chen",
"inputEmail": "sarah.chen@pinnacleind.com",
"inputCompany": "Pinnacle Industries",
"inputDomain": "pinnacleind.com",
"mobilePhone": "+1 (415) 555-0182",
"directDial": "+1 (212) 555-0394",
"companyPhone": "+1 (650) 555-0271",
"allPhoneNumbers": [
"+1 (415) 555-0182",
"+1 (212) 555-0394",
"+1 (650) 555-0271"
],
"phoneSource": "pdl",
"fullName": "Sarah Chen",
"jobTitle": "VP of Sales",
"email": "sarah.chen@pinnacleind.com",
"enrichedAt": "2026-03-23T14:22:11.341Z",
"confidence": {
"score": 0.86,
"level": "high",
"components": [
{ "name": "pdlMatch", "weight": 0.4, "value": 1.0, "contribution": 0.4 },
{ "name": "phoneCount", "weight": 0.2, "value": 1.0, "contribution": 0.2 },
{ "name": "sourceQuality", "weight": 0.25, "value": 1.0, "contribution": 0.25 },
{ "name": "identifierStrength", "weight": 0.15, "value": 0.9, "contribution": 0.135 }
],
"coldStart": false
},
"confidenceLevel": "high",
"contactabilityScore": {
"score": 92,
"level": "high",
"components": {
"phonePresence": 25,
"sourceQuality": 20,
"validationQuality": 20,
"identityStrength": 14,
"freshness": 6,
"decisionAlignment": 10
}
},
"phoneValidation": {
"isValidFormat": true,
"e164": "+14155550182",
"country": "US",
"lineType": "mobile",
"confidence": 0.95,
"originalInput": "+1 (415) 555-0182"
},
"channelStrategy": {
"primary": "phone",
"secondary": "email",
"reason": "Validated mobile number — phone-first outreach maximises connect rate."
},
"sequenceFit": {
"callFirst": true,
"smsAllowed": true,
"emailFallback": true,
"voicemailReady": true
},
"coverageAnalysis": {
"attemptedSources": ["pdl", "website"],
"successfulSources": ["pdl"],
"missedOpportunities": [],
"coverageScore": 0.85
},
"recommendedAction": {
"actionId": "place-call-now",
"label": "Place outbound call to +1 (415) 555-0182",
"owner": "SDR",
"eta": "within 1 hour",
"costEstimate": 1.5,
"executionHint": { "blocking": false }
},
"agentContract": {
"decision": "call-now",
"confidence": 0.86,
"nextAction": "Place outbound call to +1 (415) 555-0182",
"costToAct": 1.5
},
"task": {
"id": "task_8d3a...",
"kind": "phone-outreach-now",
"target": "+1 (415) 555-0182",
"owner": "sdr",
"deadline": "2026-03-23T15:22:11.341Z",
"dryRun": true,
"shouldAct": true
},
"pipelineState": { "enriched": true, "emailVerified": true, "intentChecked": false, "crmSynced": false, "deduped": false },
"actorGraph": { "previous": null, "current": "ryanclinton/phone-number-finder", "next": ["ryanclinton/bulk-email-verifier", "ryanclinton/hubspot-lead-pusher"] },
"executionReadiness": { "score": 100, "readyForOutreach": true, "blockers": [], "stepsToReady": [] },
"improvementSuggestions": [],
"salesTrust": {
"trustScore": 90,
"reasons": [
"PDL returned a personal mobile number matched to the individual.",
"Identifiers strong enough that PDL likelihood ≥ threshold.",
"Multiple phones returned across sources — corroborates reachability.",
"Backed by a structurally valid work email."
]
},
"dataHygiene": { "score": 100, "severity": "clean", "criticalIssues": [], "normalisationIssues": [], "automationSafe": true },
"sla": { "routeTo": "sdr", "respondWithinHours": 1, "reason": "High-confidence call-now — SDR ownership.", "breachRisk": "low" },
"summary": "Call Sarah Chen at +1 (415) 555-0182 (confidence high, source pdl).",
"plainEnglishSummary": "Call Sarah Chen at +1 (415) 555-0182 (confidence high, source pdl).",
"whyThisMatters": "High-confidence personal phone match. Direct conversation skips email queues entirely.",
"whyNow": "Phone numbers go stale fast. Connect rates drop ~12% per week after identification."
}

The final item in every dataset is a summary record:

{
"type": "summary",
"totalProcessed": 47,
"phonesFound": 34,
"notFound": 13,
"pdlOutOfCredits": false,
"spendingLimitReached": false,
"completedAt": "2026-03-23T14:28:03.217Z"
}

Output fields

Suite contract

FieldTypeDescription
schemaVersionstringOutput schema version ("2.0.0" — additive across minor versions)
recordTypestringphone-lookup for results, summary for the run summary, error for failure records
entityIdstringStable cross-suite canonical id (sha256 of canonical input identity). Suite-aligned name; same join key as waterfall-contact-enrichment, bulk-email-verifier, company-deep-research, and lead-enrichment-pipeline.
eventIdstringLegacy alias of entityId (same value). Kept for back-compat with existing downstream pipelines.
decisionstringcall-now / call-later / enrich-first / skip — the routing scalar to branch on

Phone fields (backwards compatible)

FieldTypeDescription
inputNamestring | nullThe name you provided
inputEmailstring | nullThe email you provided
inputCompanystring | nullThe company you provided
inputDomainstring | nullThe domain you provided
mobilePhonestring | nullPersonal mobile number from PDL (mobile_phone field)
directDialstring | nullFirst non-mobile number from PDL; most likely a direct office line
companyPhonestring | nullSecond PDL number, or first website-scraped number when PDL returned nothing
allPhoneNumbersstring[]All unique phone numbers found across both sources
phoneSourcestringpdl — matched in PDL database; website — scraped from website only; not_found — no phones found
fullNamestring | nullFull name from PDL (only when includePersonDetails: true)
jobTitlestring | nullCurrent job title from PDL (only when includePersonDetails: true)
emailstring | nullWork email from PDL (only when includePersonDetails: true)
enrichedAtstringISO 8601 timestamp of when this record was processed
confidenceobject`{ score (0-1), level (high
confidenceLevelstringBackward-compat alias for confidence.level

Contactability Intelligence layer

FieldTypeDescription
contactabilityScoreobject`{ score (0-100), level (high
phoneValidationobject | nullPrimary phone validated with libphonenumber-js: { isValidFormat, e164, country, lineType, confidence (0-1), originalInput }. Country hint inferred from the company domain TLD.
allPhoneValidationsarrayValidation block for every phone in allPhoneNumbers, in the same order.
channelStrategyobject`{ primary (phone
sequenceFitobject{ callFirst, smsAllowed, emailFallback, voicemailReady } — booleans downstream cadence tools branch on without parsing prose.
coverageAnalysisobject{ attemptedSources, successfulSources, missedOpportunities, coverageScore (0-1) } — explains which lookup paths were tried, which hit, and which sibling actors could lift coverage further.

SDR-grade decision primitives (v2.1)

FieldTypeDescription
reachabilityobject`{ status (high
callOutcomePredictionobject`{ likelyOutcome (connect
humanTimeValueobject`{ score (0-100), tier (high
contactRiskGateobject{ shouldBlockOutreach, riskReasons[], safeChannels[], unsafeChannels[], overrideAllowed } — compliance gate naming channels to block, not just allow. Use shouldBlockOutreach: true as a hard automation gate.
phoneRankingarray[{ number, e164, rank, reason, lineType, role }, ...] — SDR-tool-friendly per-number ranking. Diallers consume in rank order.
nextBestActionsarray[{ if: "voicemail", then: "send_sms" }, ...] — conditional if/then tree dialler/cadence tools branch on at runtime.
dataIntegrityobject`{ status (clean
coverageCeilingobject{ maxPossibleScore (0-1), currentScore, gap, howToClose[] } — explicit gap math: "you could reach X by running these sibling actors." Drives suite usage with a hard number.
automationTriggersobject`{ sendToDialer, sendToSms, sendToCrm, sendToEmailSequence, requiresEnrichment, priorityQueue (high
slaTierobject`{ tier (P1

Decision layer

FieldTypeDescription
decisionSignalsstring[]Stable enum tokens (e.g. pdl-mobile-match, strong-identifiers, multi-phone-record) — branch on these in SQL/Sheets/agent filters.
negativeSignalsstring[]Plain-language risk surface — every concrete reason this record might fail outreach. Empty array = no concerns.
isContactablebooleanTrue when at least one channel (phone or email) is identified.
isCallablebooleanTrue when at least one phone number was identified.
riskScore / riskLevelnumber / string0-100 risk of acting on this record being a wasted touch + low/medium/high band.
decisionRiskobject{ downsideIfWrong, upsideIfRight, asymmetryRatio }.
signalIndependenceobject{ score, distinctSourceCount, totalComponentCount, interpretation, warning? }. Catches the "looks like 4 corroborating signals but really 1 echoed 4 times" trap. Aligned with waterfall-contact-enrichment and company-deep-research.
counterfactualobject{ droppedComponent, withoutThisSignal: { score, level, decision }, interpretation }. Drops the highest-weight confidence component and recomputes — tells you whether the call decision is load-bearing on one signal or diversified.
decisionMemoryobject|nullCloses the feedback loop when lastAction is provided as input. { outcome: 'engaged' | 'no-response' | 'no-change' | 'resolved' | 'too-soon-to-tell', daysSinceAction, confidence, inferenceMethod, epistemicStatus }. Honest: only signal-change is observable.
failureTypestring | nullpdl_no_match / pdl_credit_exhausted / identifier_too_sparse / website_blocked / auth / etc.
failureContextobject | null{ failureType, confidenceLossReason, retryLikelihood }.
scoringTracearrayPer-rule contribution breakdown — reproduces the confidence score line by line.
recommendedActionobject{ actionId, label, owner, eta, costEstimate, executionHint }.
recoveryPlanobject | nullWhen this lookup couldn't deliver: { nextBestActorSlug, rationale, expectedLift, recommendedFlow[] (ordered sibling-actor slugs to chain), expectedLiftScore (0-1) }.
taskobjectUniversal task: { id, kind, target, payload, owner, deadline, dryRun, shouldAct }. dryRun: true by default.
agentContractobjectCompact agent-facing surface: { decision, confidence, nextAction, costToAct }.
whyThisMatters / whyNow / summary / plainEnglishSummarystringPlain-English explanations + ≤280-char LLM-friendly summary.
methodologystringDisclosure: scoring is heuristic-derived, not produced by a trained model.

Suite navigation (Section N)

FieldTypeDescription
pipelineStateobject{ enriched, emailVerified, intentChecked, crmSynced, deduped } — what HAS been done already on this contact.
actorGraphobject{ previous, current, next[] } — ranked sibling actors to chain to next.
executionReadinessobject{ score, readyForOutreach, blockers, stepsToReady } — the boolean automation should branch on.
improvementSuggestionsarrayTop-3 score-lift suggestions with projected delta + sibling actor pointers.
dataGapsarrayPer-field gap analysis with sibling-actor recovery pointers.
cardsarrayUI-ready cards: { title, severity, action }.

Trust (Section P)

FieldTypeDescription
salesTrustobject{ trustScore (0-100), reasons[], repObjection?, answer? } — explainability for SDR adoption.
dataHygieneobject{ score, severity, criticalIssues, normalisationIssues, automationSafe }. automationSafe: true is the production gate.
slaobject{ routeTo, respondWithinHours, reason, breachRisk } — response-time routing for inbound queue automation.

Optional / opt-in blocks

FieldWhen emittedDescription
identityenableDedup: true{ canonicalDomain, duplicateCount, duplicateRunIndices, isCanonical }.
temporalSignalswatchlistName set{ trend, scoreDelta, momentumScore, runsSeen, reengage, volatility }. First run carries trend: "new".
expectedValueenableEconomics: true{ conversionProbability, estimatedDealSizeUsd, expectedRevenueUsd, costToActUsd, expectedRoi, proxy, explanation }.
priorityScoreRoienableEconomics: true0-100 ROI-aware priority — distinct from confidence-based ranking.
actionDecisionenableEconomics: true{ type: act|delay|ignore, reason, minRoiToAct } — orthogonal to qualification.
actionPlanalwaysMulti-step downstream sequence with required flags + cost estimates.
timingWindowalways{ status, reason, decayRisk }.
relativePositionalways{ tier (top-1%..bottom-50%), competitiveRank, shouldPrioritise }.
disqualificationAnalysisdecision in [skip, enrich-first]{ primaryReason, secondaryReasons, recoverable, pathToQualify? }.
upstreamQualityprevious actor detected{ sourceActor, confidence, knownWeakness? }.
allocationDecisionconstraints set{ selected, reason, excludedDueTo?, rankInAllocation? }.
simulationsimulate set{ overrideWeights, newScore, delta, decisionChange? }.
freshnessfreshness configured / detected{ status, ageDays, scorePenalty, recommendedAction, dateFieldUsed }.
contradictionscontradictions presentPairwise signal contradictions surfaced for review.
openingAnglecallable contactsPer-contact micro-personalisation hint for cold-call openers.

Run summary record

The final dataset item has recordType: "summary" and includes:

  • decisionCounts (call-now / call-later / enrich-first / skip), riskLevelCounts, callableCount
  • notifications[] — UI-surfaceable run-level alerts
  • icpInsights — passive top-performer insights from this run's cohort (when ≥5 records score ≥0.75)
  • cohortInsights — overfit / diversity / dedup observations
  • accountReadiness[] — per-company rollup (when enableAccountRollup: true)
  • calibration — score-band priors + grade (A–F based on cohort size + outcome alignment)
  • outcomeValidation — when outcomeDatasetId is supplied: win-rate by decision + predictive flag
  • totalExpectedRevenueUsd, totalCostToActUsd, portfolioRoi (when economics is enabled)
  • allocation — full allocation summary (when constraints set)
  • simulationSummary — what-if rollup (when simulate set)
  • savings — cost / touches avoided by allocation rules

How much does it cost to find phone numbers?

Phone Number Finder uses pay-per-event pricing — you pay $0.10 per successful phone lookup. Records where no phone number is found are free. Platform compute costs are included.

ScenarioLookupsCost per lookupTotal cost
Quick test1$0.10$0.10
Small batch10$0.10$1.00
Medium batch100$0.10$10.00
Large batch500$0.10$50.00
Enterprise1,000$0.10$100.00

You can set a maximum spending limit per run in the Run options panel to control costs. The actor stops and flushes results when your budget is reached — you are never charged more than you approve.

Compare this to Clay at $0.80–$7.50 per phone lookup (depending on credit tier), Lusha at $1.00+ per contact, or ZoomInfo at $150+/month for a seat licence. Most teams using Phone Number Finder spend $5–$20/month with no subscription commitment and no per-seat fees.

Find phone numbers using the API

Python

from apify_client import ApifyClient
client = ApifyClient("YOUR_API_TOKEN")
run = client.actor("ryanclinton/phone-number-finder").call(run_input={
"persons": [
{
"name": "Marcus Webb",
"email": "marcus.webb@deltaventures.io",
"company": "Delta Ventures",
"domain": "deltaventures.io"
},
{
"name": "Sarah Chen",
"company": "Pinnacle Industries",
"domain": "pinnacleind.com"
}
],
"scrapeWebsites": True,
"includePersonDetails": True
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item.get("type") == "summary":
print(f"Summary: {item['phonesFound']} phones found out of {item['totalProcessed']}")
elif item.get("mobilePhone"):
print(f"{item['inputName']} — mobile: {item['mobilePhone']} | confidence: {item['confidence']}")
elif item.get("companyPhone"):
print(f"{item['inputName']} — company: {item['companyPhone']} | source: {item['phoneSource']}")
else:
print(f"{item['inputName']} — not found")

JavaScript

import { ApifyClient } from "apify-client";
const client = new ApifyClient({ token: "YOUR_API_TOKEN" });
const run = await client.actor("ryanclinton/phone-number-finder").call({
persons: [
{
name: "Marcus Webb",
email: "marcus.webb@deltaventures.io",
company: "Delta Ventures",
domain: "deltaventures.io"
},
{
name: "Sarah Chen",
company: "Pinnacle Industries",
domain: "pinnacleind.com"
}
],
scrapeWebsites: true,
includePersonDetails: true
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const item of items) {
if (item.type === "summary") {
console.log(`Summary: ${item.phonesFound}/${item.totalProcessed} phones found`);
} else if (item.mobilePhone) {
console.log(`${item.inputName}: mobile=${item.mobilePhone}, confidence=${item.confidence}`);
} else if (item.companyPhone) {
console.log(`${item.inputName}: company=${item.companyPhone}, source=${item.phoneSource}`);
} else {
console.log(`${item.inputName}: not found`);
}
}

cURL

# Start the actor run
curl -X POST "https://api.apify.com/v2/acts/ryanclinton~phone-number-finder/runs?token=YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"persons": [
{
"name": "Marcus Webb",
"email": "marcus.webb@deltaventures.io",
"company": "Delta Ventures",
"domain": "deltaventures.io"
}
],
"scrapeWebsites": true,
"includePersonDetails": true
}'
# Fetch results once the run completes (replace DATASET_ID from the run response)
curl "https://api.apify.com/v2/datasets/DATASET_ID/items?token=YOUR_API_TOKEN&format=json"

How Phone Number Finder works

Step 1 — People Data Labs enrichment

The actor calls the PDL Person Enrich API (https://api.peopledatalabs.com/v5/person/enrich) with the identifiers you provide. Query parameters are built from whichever of name, email, company, and domain are present. The request includes min_likelihood=6 — PDL's internal match quality score from 0–10 — which filters out weak or ambiguous matches and returns only records PDL is reasonably confident belong to the same person.

PDL's response includes a mobile_phone field (the individual's personal mobile) and a phone_numbers array (all associated numbers). The actor maps mobile_phone to mobilePhone, the first non-mobile number to directDial, and the second non-mobile to companyPhone. Confidence is set to high if a mobile is returned, medium otherwise.

Step 2 — Website scraping fallback

When PDL returns no phones (or PDL credits are exhausted), the actor scrapes up to six pages of the company's website: the homepage, /contact, /contact-us, /about, /about-us, and /team. Each page is fetched with a 10-second timeout and a real browser User-Agent string.

Phone numbers are extracted using an international regex pattern that matches most global formats. Matches are then filtered through two additional steps: a digit-count check (minimum 7 digits, eliminating ZIP codes, years, and short numeric strings) and a fax context filter (a 60-character window around each match is checked for fax, facsimile, f:, fx:, and similar strings). Remaining numbers are deduplicated by normalised digit string before being assigned to companyPhone with low confidence.

Step 3 — Result assembly and output

If both PDL and website scraping find numbers, the actor merges them into allPhoneNumbers using the same digit-normalised dedup key, preserving the PDL numbers as primary classifications. The result object echoes all four input identifiers so output rows can be matched back to your input list without a separate join step.

The PPE charge fires after the result is assembled, but only when phoneSource !== 'not_found'. Batch flushing to the Apify Dataset happens every 100 records. A 200ms inter-person delay prevents rate-limiting on both the PDL API and target websites.

Error handling and resilience

Network failures use exponential backoff (up to 2 retries). PDL 402 errors (out of credits) disable PDL for the remainder of the run so website scraping continues uninterrupted. PDL 401/403 errors (bad API key) stop the run immediately with a clear error message. A 429 from any host triggers 2^attempt × 2000ms backoff before retrying. Invalid person queries (missing all required identifiers) are skipped with a warning rather than crashing the run.

Tips for best results

  1. Supply email as the primary identifier. Email is PDL's strongest single identifier. A name alone has high collision probability; name + email reduces false positives to near zero.
  2. Add domain whenever you have it. Even when you provide an email, supplying the domain as well gives PDL an additional signal to resolve ambiguous name-email combinations.
  3. Enable website scraping for SMB lists. Businesses under ~50 employees are often under-indexed in PDL. Website scraping is the primary phone source for local businesses, tradespeople, and regional companies.
  4. Use includePersonDetails: true for CRM enrichment. When running against an existing contact list, the job title and work email fields from PDL let you verify you matched the right person before importing.
  5. Set a spending cap for large batches. Open the Run options before starting and set a maximum spend. At $0.10/lookup with ~70% typical hit rates, 1,000 contacts costs around $70 — set $80 as a ceiling with room for variance.
  6. Combine with email finding first. If you only have company domains, run Email Pattern Finder first to generate likely email addresses, then pass those emails into this actor for maximum PDL match rates.
  7. Pipe results into your CRM via webhook. Configure an Apify webhook to POST completed dataset items directly to your HubSpot, Salesforce, or Pipedrive API endpoint — no manual CSV import required.
  8. Schedule monthly refreshes. Phone numbers go stale as contacts change jobs. A monthly scheduled run on your active prospect list keeps numbers current without any manual effort.

Use in Dify

Drop this actor into Dify workflows via the Apify plugin's Run Actor node. Each lookup returns scored, classified, and routed as structured JSON — call-now / call-later / enrich-first / skip plus the recommendedAction.actionId enum your downstream node branches on. Competitors pointed at the same data return raw phone strings; this returns decisions.

  • Actor ID: ryanclinton/phone-number-finder
  • Sample input (call-decision routing for a sales prospect list):
{
"persons": [
{ "name": "Marcus Webb", "email": "marcus.webb@deltaventures.io", "domain": "deltaventures.io", "industry": "software" },
{ "name": "Sarah Chen", "company": "Pinnacle Industries", "domain": "pinnacleind.com", "industry": "manufacturing" }
],
"mode": "balanced",
"persona": "outbound-sdr",
"goal": "quick-wins",
"scorecardTemplate": "b2b-saas-cold-call",
"outputProfile": "standard",
"enableEconomics": true
}
  • Branching example — wire a Dify if/else node on the v2.1 SDR primitives:
    • recordType = "phone-lookup" AND contactRiskGate.shouldBlockOutreach = true → halt automation; route to human review queue (compliance gate)
    • recordType = "phone-lookup" AND slaTier.tier = "P1" → top-priority dial queue: AE-owned, 1-hour SLA
    • recordType = "phone-lookup" AND slaTier.tier = "P2" AND automationTriggers.sendToDialer = true → standard SDR dial queue, prefilled with phoneRanking[0].e164
    • recordType = "phone-lookup" AND humanTimeValue.tier = "high" AND callOutcomePrediction.likelyOutcome = "connect" → high-value mobile dial — skip the queue, dial now
    • recordType = "phone-lookup" AND callOutcomePrediction.likelyOutcome = "voicemail" AND automationTriggers.sendToSms = true → bypass voicemail; SMS instead
    • recordType = "phone-lookup" AND humanTimeValue.tier = "avoid" → archive; not worth a human touchpoint
    • recordType = "phone-lookup" AND decision = "enrich-first" → fan out across recoveryPlan.recommendedFlow[] in order (e.g. email-pattern-finderbulk-email-verifier → re-run phone-number-finder), then re-evaluate
    • recordType = "phone-lookup" AND dataIntegrity.status = "conflicted" → halt automation; surface to human reviewer with dataIntegrity.issues[]
    • recordType = "phone-lookup" AND channelStrategy.primary = "email" → skip phone, route to your email cadence using the verified email
    • recordType = "phone-lookup" AND decision = "skip" → archive; identifiers are too sparse to act on
    • recordType = "summary" → read headline + oneLine + decisionCounts for the run-level Slack/email digest
  • Opt-in modes Dify workflows can leverage:
    • watchlistName (cross-run state) — every record from run 2 onwards carries temporalSignals with trend (rising / falling / stable / unchanged / new) and reengage: true flag for contacts whose decision flipped from skip / enrich-first to call-now / call-later since the last run. Branch on reengage = true to surface re-engaged leads in scheduled monitoring loops.
    • enableEconomics: true — adds expectedValue.expectedRoi and actionDecision.type (act / delay / ignore) per record. ROI-aware automation: branch on actionDecision.type = "act" for production gating.
    • constraints: { budgetUsd, maxOutreachPerRun } — greedy ROI-first allocator selects records under all caps; each record gets allocationDecision.selected: true/false so Dify can route only the selected leads downstream.
    • outputProfile: "llm" — strips heavy diagnostic blocks; ideal when the next Dify node is an LLM that needs decision, summary, recommendedAction only.
  • Action arrays usable verbatim — no LLM rewriting required: actionPlan[] (multi-step downstream sequence with targetActorSlug + estimatedCostUsd per step), improvementSuggestions[] (top-3 score-lift suggestions with sibling-actor pointers), dataGaps[].suggestedFix (sibling actor slug to chain when a field is missing), cards[] (UI-ready {title, severity, action} for Slack/dashboard rendering), and the universal task[] schema ({id, kind, target, payload, owner, deadline, dryRun: true, shouldAct}) — wire-compatible with Jira / Linear / GitHub Issues without parsing prose.

Combine with other Apify actors

ActorHow to combine
Email Pattern FinderGenerate likely email addresses from company domains first, then pass those emails into Phone Number Finder for higher PDL match rates
Website Contact ScraperScrape all contact info (emails, phones, socials) from a business website in one pass — use instead of Phone Number Finder when you need emails too
Google Maps Email ExtractorExtract local business leads from Google Maps, then enrich with phones using this actor for a complete call-ready prospect list
Waterfall Contact Enrichment10-step enrichment cascade for contacts where you need emails, phones, and LinkedIn — use when you need full profiles rather than phones alone
B2B Lead Gen SuiteFull pipeline from URL list to scored leads — Phone Number Finder slots in as the phone enrichment step post-qualification
Bulk Email VerifierVerify the work emails returned by includePersonDetails: true before sending outreach
HubSpot Lead PusherPush enriched phone records directly into HubSpot contacts after a Phone Number Finder run completes
Lead Enrichment PipelineAll-in-one Clay alternative: email discovery, verification, company research, and scoring in one run ($0.12/lead)
AI Outreach PersonalizerGenerate personalized cold emails using your own OpenAI/Anthropic key — zero AI markup ($0.01/lead)
Intent Signal TrackerTrack buying signals: hiring, tech changes, funding, content updates. Prioritize outreach by intent score ($0.05/company)
Lead Data Quality AuditorAudit lead data quality before outreach — email verification, phone validation, domain freshness ($0.005/record)

Limitations

  • PDL coverage is strongest for US and English-speaking markets. Phone number coverage for contacts in continental Europe, Asia, and Latin America is lower. Website scraping partially compensates but company sites in non-English languages may structure contact pages differently.
  • PDL mobile numbers are not available for all profiles. PDL's mobile_phone field is populated for a subset of their database. Many matches will return a direct dial or company number but no mobile. Confidence will be medium rather than high for these records.
  • Website scraping returns company phones, not personal numbers. Numbers found via the website fallback are general company lines, not the individual's direct dial. Use these for initial company contact, not personal outreach.
  • PDL match requires at least one strong identifier. Name alone (without domain, company, or email) will be rejected before the PDL query is sent. Incomplete person objects are skipped with a warning in the logs.
  • No JavaScript rendering on website scraping. The website fallback uses plain HTTP fetch. Sites that load phone numbers via JavaScript after page load will not have those numbers captured. The Website Contact Scraper Pro handles JS-rendered sites.
  • 10-second timeout per web page. Very slow websites may time out without returning phone data. The actor continues to the next person rather than hanging the run.
  • Built-in PDL key has shared rate limits. For batches over 200 persons, bring your own PDL API key to avoid hitting shared rate limits mid-run.
  • Phone numbers are not verified for active status. Numbers are returned as-found in PDL or on the website. Use a separate verification step before SMS campaigns where deliverability is critical.

Integrations

  • Zapier — trigger a Phone Number Finder run when a new lead is added to a Google Sheet or CRM, then write the phone number back automatically
  • Make — build multi-step scenarios: enrich a lead list, filter by confidence: "high", then push mobile numbers to an SMS platform
  • Google Sheets — export completed phone datasets directly to Sheets for team review or further processing
  • Apify API — call the actor programmatically from any language; pass dynamic person lists from your CRM or data pipeline
  • Webhooks — fire a POST to your HubSpot or Salesforce endpoint the moment a run completes, with the full dataset payload
  • LangChain / LlamaIndex — use the Apify Loader to feed phone-enriched contact records into an AI sales assistant or lead qualification agent

Troubleshooting

  • Records coming back as not_found for known contacts — This usually means PDL does not have a record for that individual, or the identifiers provided are too sparse for a confident match. Add more identifiers (especially email) and check that company names are spelled correctly. Enable scrapeWebsites: true as a fallback.
  • PDL auth error in the logs — If you supplied a custom pdlApiKey and see a 401 or 403 error, the key is invalid or expired. Remove pdlApiKey from the input to use the built-in key, or regenerate your key at the PDL dashboard.
  • Website scraping finding no numbers — Some company sites load phone numbers via client-side JavaScript rather than static HTML. The HTTP-based scraper cannot execute JavaScript. For these sites, consider the Website Contact Scraper Pro which uses a full browser.
  • Run stopped early with spendingLimitReached: true in summary — Your per-run spending cap was reached. Increase the limit in Run options, or split your list into smaller batches across multiple runs.
  • Slow run times for large batches — The 200ms inter-person delay and website scraping (up to six pages per domain when PDL misses) add up for large lists. Disable scrapeWebsites for the fastest possible run when PDL coverage is sufficient for your use case.

Responsible use

  • This actor only accesses publicly available contact information and the People Data Labs database, which aggregates data from public sources.
  • Respect website terms of service and robots.txt directives when website scraping is enabled.
  • Comply with GDPR, CCPA, CAN-SPAM, TCPA, and other applicable data protection laws when using phone numbers for outreach, especially for SMS and cold calling.
  • Do not use extracted phone numbers for spam, robocalling, harassment, or unauthorized commercial contact.
  • For guidance on web scraping legality, see Apify's guide.

FAQ

How many phone numbers can I look up in one run? The default cap is 100 persons per run, configurable up to 1,000 via the maxPersons parameter. For larger batches, either increase maxPersons or split across multiple runs. There is no hard platform limit beyond what your spending cap allows.

How accurate are the phone numbers returned by Phone Number Finder? PDL-sourced numbers (confidence high or medium) are drawn from their aggregated public database and are generally accurate for currently employed professionals. Mobile numbers from PDL have the highest individual match accuracy. Website-scraped numbers (confidence low) are correct as of the scrape date but are company lines rather than personal numbers.

Does Phone Number Finder return personal mobile numbers or just company switchboards? Both. PDL's mobile_phone field is a personal mobile number matched to the individual. When PDL returns only a direct dial or the website fallback is used, you will get a company or direct office line rather than a personal mobile.

What identifiers does each person entry need? Each person needs at least one of: email alone, name + domain, or name + company. Email is the strongest single identifier. Name alone is not accepted because it is too ambiguous for PDL to return a confident match.

How is Phone Number Finder different from Clay or Lusha? Clay charges $0.80–$7.50 per enrichment credit depending on your plan tier. Lusha charges $1.00+ per contact export. Both require monthly subscriptions. Phone Number Finder charges $0.10 per successful lookup with no subscription — you pay only when a number is found. The same PDL database underpins the lookup in both cases.

Can I bring my own People Data Labs API key? Yes. Supply your PDL key in the pdlApiKey field. This is useful for teams with an existing PDL subscription who want higher rate limits or to use their own PDL credit allocation. If omitted, the built-in key is used at no extra charge.

What happens when PDL runs out of credits mid-run? The actor detects the 402 response from PDL and disables PDL lookups for the rest of the run. Website scraping continues for remaining records, so you still get company phone numbers for contacts whose domains are present. The summary record includes a pdlOutOfCredits: true flag.

Is it legal to scrape phone numbers from websites? Scraping publicly available phone numbers from business websites is generally lawful in most jurisdictions. However, using those numbers for unsolicited commercial contact may be regulated by TCPA (US), PECR (UK), or GDPR (EU). Always ensure your outreach complies with applicable telemarketing and data protection laws. See Apify's scraping legality guide for more detail.

Can I schedule Phone Number Finder to run automatically? Yes. Use the Apify Scheduler to trigger runs on a daily, weekly, or custom cron schedule. This is useful for refreshing a prospect database monthly or monitoring new contacts added to a CRM.

How long does a typical run take? A batch of 100 persons takes approximately 3–5 minutes with website scraping enabled. With scrapeWebsites: false, the same batch runs in under 2 minutes. Time scales roughly linearly with batch size and the proportion of contacts that require website fallback.

What does the confidence field mean, and how should I use it? high means PDL returned a personal mobile number — the strongest match. medium means PDL returned a non-mobile number (direct dial or office line). low means the number came from website scraping only, with no individual-to-number match. For outreach prioritisation, route high confidence records to personalised SMS or direct calling sequences, and low confidence records to general company contact flows.

Can I use this actor with Make or Zapier? Yes. Both integrations are supported natively through the Apify platform. In Make, use the Apify "Run Actor" module to trigger a run with your person list and the "Watch Dataset Items" module to process results. In Zapier, the Apify integration lets you trigger runs and retrieve dataset items as part of multi-step zaps.

Help us improve

If you encounter issues, you can help us debug faster by enabling run sharing in your Apify account:

  1. Go to Account Settings > Privacy
  2. Enable Share runs with public Actor creators

This lets us see your run details when something goes wrong, so we can fix issues faster. Your data is only visible to the actor developer, not publicly.

Support

Found a bug or have a feature request? Open an issue in the Issues tab on this actor's page. For custom solutions or enterprise integrations, reach out through the Apify platform.