# Phone Number Validator & Enrichment (bulk, 200+ countries) (`everyotherfriday/phone-validator`) Actor

Clean a phone list in one run: validity, formatting in three styles, number type, country, region, carrier and timezones for 200+ countries, plus optional discovery of phone numbers on company websites. No API keys needed. Built for CRM hygiene, lead enrichment and SMS prep.

- **URL**: https://apify.com/everyotherfriday/phone-validator.md
- **Developed by:** [Paul Vasquez](https://apify.com/everyotherfriday) (community)
- **Categories:** Lead generation, Business, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 number validateds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Phone Number Validator and Enrichment

Normalize phone lists, inspect numbering-plan validity, and discover telephone
numbers on public homepages and contact pages. This Python 3.12 Apify actor uses
the offline `phonenumbers` port of libphonenumber. List processing requires no
network, API key, provider account, or paid lookup service. Optional website
discovery uses HTTP requests and HTML parsing without a browser.

### What validation means

`valid` means that a parsed number matches the library's numbering-plan metadata.
`possible` primarily checks whether its length is plausible. A possible number
can still be invalid. A valid number can be unassigned, disconnected, or unable
to receive messages. This actor performs **no HLR lookup and makes no “is active”
claim**. It does not call numbers, send SMS, identify subscribers, or verify
ownership. Metadata may lag numbering-plan changes.

Carrier information describes the original allocation where available. Number
portability means it may not identify the current carrier. Geographic descriptions
refer to numbering areas, not a person's present location. Timezones are candidate
zones from metadata, not a device location. Missing enrichment is returned as
null or an empty array instead of an invented value.

### Input

`phoneNumbers` accepts up to 10,000 strings. List order and duplicates are
preserved, so each input has a corresponding result unless a charging limit
prevents additional paid output. `defaultRegion` defaults to `US`; use an ISO
region such as `GB`, `DE`, or `IN` for national numbers. International numbers
starting with `+` carry their own country calling code.

`websiteUrls` optionally accepts up to 100 HTTP(S) URLs. Each URL is normalized
to its origin homepage, followed by the origin's `/contact` path. Credentials
in URLs are rejected. Repeated normalized sites are scanned once. The actor
follows up to five redirects, but does not discover alternative contact links,
execute JavaScript, solve access challenges, or crawl a site's other pages.

`outputFormat` is `e164`, `international`, or `national`, defaulting to `e164`.
It selects the additional `formatted` field; all three explicit format fields
remain present. `includeCarrier` defaults to true. `includeGeo` defaults to true
and controls geographic description and timezones; country and region remain
available. `timeoutSecs` bounds each complete page attempt, from 1 to 60 seconds.
The supplied daily input uses five seconds and 15 mixed numbers plus Python.org
and Apify, requiring no keys and completing comfortably under two minutes in
the recorded validation.

### Results

Number rows contain `input`, `source`, `valid`, `possible`, `e164`,
`international`, `national`, `formatted`, `countryCode`, `region`, `regionName`,
`numberType`, `carrier`, `location`, `timezones`, `isMobile`, `isTollFree`,
`isVoip`, and `error`. `countryCode` is numeric; `region` is a library region
code. Non-geographic numbers may use region `001`. Descriptions are English.
Types include mobile, fixed line, fixed-line-or-mobile, VoIP, toll-free, premium
rate, and unknown. `isMobile` includes the ambiguous fixed-line-or-mobile type;
consult `numberType` when the distinction matters.

Website number rows additionally include `websiteUrl`, `foundOn`, and `context`.
Context contains the matched text with up to 40 characters on either side.
Scripts, styles, and noscript content are excluded; visible text and telephone
links are matched with `PhoneNumberMatcher` using POSSIBLE leniency. E.164
deduplication applies within each website across both pages, retaining the first
occurrence. Extensions sharing the same E.164 number collapse together. Numbers
from separate websites or the explicit list remain separate results.

Filter `rowType=number` when exporting only telephone results. Each attempted
unique website also produces an uncharged `website-summary` row with fetched-page
count, discovered-number count, failures, and timing. `SUMMARY` in the key-value
store records aggregate counts and eligible billing events. A successful fetch
with zero matches is an ordinary empty result, not proof the business has no phone.

### Pricing and failures

`number-validated` costs $0.0008 per parseable number row, whether valid or
invalid. Empty and unparseable strings produce uncharged error rows.
`website-scanned` costs $0.002 once per website with at least one successfully
fetched HTML page, including websites with zero matches. It is not charged per
page. Discovered parseable numbers also incur the number event. Summary rows
themselves are always free; completely failed websites incur no website event.

HTTP 429, server errors, and transport failures receive two retries with one-
and two-second backoff. Other HTTP errors are not retried. Responses exceeding
two megabytes or non-HTML responses are rejected. Partial website results survive
page failures. Charging limits stop further events of that type. Charging occurs
before persistence, so storage failures cannot be automatically rolled back.

### Running and verification

Create a Python 3.12 virtual environment, install `requirements.txt`, then run:

```powershell
apify validate-schema .actor/input_schema.json
.venv/Scripts/python.exe -m unittest discover -s tests -v
powershell -NoProfile -ExecutionPolicy Bypass -File validation/run_live.ps1
```

The live harness creates isolated local storage and records results and timings
under `validation/`. Local SDK charges are ignored, not actual billing. Before
deployment, configure both custom events from `.actor/pay_per_event.json` in
Console and disable synthetic charges. See `VALIDATION.md` for measured evidence
and remaining platform checks. Library behavior is documented in the
[upstream project](https://github.com/daviddrysdale/python-phonenumbers); charging
uses the [Apify SDK](https://docs.apify.com/sdk/python/reference/class/Actor).

### Example output

Recorded local validation output from [validation/results.json](validation/results.json), the first item in `rows`. Fields are omitted for brevity; retained values are unchanged. This is a historical example, not a current-source claim.

```json
{
    "rowType":  "number",
    "input":  "+1 202-555-0123",
    "source":  "list",
    "valid":  true,
    "possible":  true,
    "e164":  "+12025550123",
    "region":  "US",
    "numberType":  "FIXED_LINE_OR_MOBILE",
    "carrier":  null,
    "error":  null
}
```

### Use cases

- A CRM administrator normalizes imported telephone strings to E.164 while routing unparseable records to a cleanup queue.
- A customer support operations manager separates toll-free, fixed-line, and ambiguous mobile classifications before reviewing contact-routing rules.
- A data quality consultant compares valid and possible flags in a client-supplied list to prioritize manual record correction.
- A business directory editor scans supplied public websites for contact numbers and reviews the retained page context before updating listings.

### Pricing example

1,000 parseable number rows and 100 successfully scanned websites cost (1,000 x $0.0008) + (100 x $0.002) = **$1.00** in declared events. The 1,000 includes any discovered numbers; extra discovered parseable rows add $0.0008 each. Rates come from [the local event declaration](.actor/pay_per_event.json). This calculation is an event subtotal, not a measured invoice; local validation does not bill.

### Limitations

The saved example comes from the validation input list, not a verified subscriber. A true validity flag establishes neither reachability nor ownership. National-format results depend on the selected region. Website discovery covers only the homepage and /contact; zero matches do not establish absence of a telephone number.

# Actor input Schema

## `phoneNumbers` (type: `array`):

Up to 10,000 input numbers. Empty and unparseable strings produce free errors.

## `websiteUrls` (type: `array`):

Up to 100 HTTP(S) websites. Fetch homepage and /contact; dedupe within each site.

## `defaultRegion` (type: `string`):

ISO region for numbers without an international prefix.

## `outputFormat` (type: `string`):

Controls formatted; all three standard fields remain available.

## `includeCarrier` (type: `boolean`):

Include original carrier metadata; not current network verification.

## `includeGeo` (type: `boolean`):

Include geographic description and timezones.

## `timeoutSecs` (type: `integer`):

Total deadline per page attempt. Two retries for 429/5xx or transport failures.

## Actor input object example

```json
{
  "phoneNumbers": [],
  "websiteUrls": [],
  "defaultRegion": "US",
  "outputFormat": "e164",
  "includeCarrier": true,
  "includeGeo": true,
  "timeoutSecs": 10
}
```

# Actor output Schema

## `numbers` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("everyotherfriday/phone-validator").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("everyotherfriday/phone-validator").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call everyotherfriday/phone-validator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,everyotherfriday/phone-validator"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/JVefR8PasoXlZRAao/builds/ZQy7nzrKqfn4MTbXt/openapi.json
