# 📞 Phone Number Validator - Line Type, Country, Bulk (`renzomacar/phone-validator`) Actor

Validate and enrich phone numbers in bulk: valid/possible, line type (mobile/landline/VoIP), country, E.164 format, and outreach risk flags. Offline libphonenumber engine, no API key. A NumVerify/Twilio Lookup alternative for cleaning lead lists.

- **URL**: https://apify.com/renzomacar/phone-validator.md
- **Developed by:** [Renzo Madueno](https://apify.com/renzomacar) (community)
- **Categories:** Lead generation
- **Stats:** 3 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 phone 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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## 📞 Bulk Phone Number Validator & Type Checker

### 🎥 Video walkthrough

https://youtu.be/sViTqFM37lQ

**Paste a list of phone numbers, get back a clean, normalized one.** This Actor
takes any list of phone numbers — messy, mixed-country, half in local format —
and returns, for each one, whether it's a *valid* number, its **E.164** form
(`+14155552671`), its national and international formatting, the **country** and
calling code, and the **line type**: mobile, landline, VoIP, toll-free, premium
rate, and more. It also flags the numbers that are **risky to include in an
outreach list** so you can drop them before they cost you.

No API key. No per-lookup metering from a third-party service. It runs on Google's
`libphonenumber` metadata **entirely offline** — no network call per number — so
it's fast and cheap on lists of any size.

***

### Why validate a phone list before you dial or text

A raw phone list — scraped, purchased, or typed by humans into a form — is full
of numbers that will burn money and hurt your campaign:

- **Invalid numbers** that were never real (typos, wrong length, wrong country).
  Your SMS platform still charges you to *attempt* them, and your dialer wastes
  time.
- **Landlines mixed into an SMS list.** A fixed line **can't receive a text** —
  send anyway and you pay for a message that goes nowhere.
- **Premium-rate and toll-free numbers.** Dialing or texting a premium-rate line
  can rack up charges; a toll-free number is an IVR/switchboard, not a person.
- **Wrong or missing country codes**, so half your list won't route.

Cleaning first means: fewer wasted SMS/call credits, higher connect rates,
consistent **E.164** formatting your CRM and SMS API actually accept, and mobile
numbers correctly separated from landlines for SMS.

***

### The lead-verification suite: find → verify → clean

This Actor is the **phone half** of a lead-hygiene pipeline. Pair it with its
siblings:

1. **Find the contacts.**
   - **Website Contact Finder** — crawl any domain and pull out emails, phones and social links.
   - **Company Enrichment** — turn a domain into tech stack, contacts and mail provider.
2. **Verify each channel before you use it:**
   - **Bulk Email Verifier** — syntax + live MX + disposable/role/free checks and a 0–100 deliverability score, for the email column.
   - **Bulk Phone Number Validator** — *this Actor* — for the phone column.
3. **Then reach out** on a clean, correctly-formatted, correctly-typed list.

The result: you stop paying your SMS and dialer tools to attempt numbers that
were never going to connect, and every number that survives is stored in one
canonical format.

***

### What each field means

| Field | What it tells you |
|---|---|
| `input` | The original string you supplied, unchanged (so you can join back to your source rows). |
| `isValid` | The number is a **valid** number under its country's numbering plan — right length *and* an allocated prefix. This is the field to filter on. |
| `isPossible` | Weaker check: the number is a **plausible length** for the country, even if the specific prefix isn't a real allocation. Useful to tell "typo" (`isPossible:false`) from "looks real but isn't allocated". |
| `e164` | The number in canonical **E.164** format (`+14155552671`) — the one format SMS APIs, CRMs and dialers universally accept. Store *this*. |
| `national` | Human-readable national format, e.g. `(415) 555-2671`. |
| `international` | Human-readable international format, e.g. `+1 415 555 2671`. |
| `country` | Detected **ISO country code** (`US`, `GB`, `DE`…), inferred from the +country code, or from your `defaultCountry` for local-format numbers. |
| `countryCallingCode` | The dialing prefix (`1`, `44`, `49`…). |
| `type` | The raw line type from `libphonenumber`: `MOBILE`, `FIXED_LINE`, `FIXED_LINE_OR_MOBILE`, `VOIP`, `TOLL_FREE`, `PREMIUM_RATE`, `SHARED_COST`, `UAN`, `PAGER`, etc. `null` when the country's plan doesn't distinguish. |
| `lineType` | Plain-English label for `type` (e.g. `Mobile`, `Landline (fixed line)`, `Toll-free`, `VoIP`). |
| `isMobile` | `true` when the number is mobile (or the ambiguous *fixed-line-or-mobile*) — i.e. a candidate for SMS. |
| `riskFlags` | Array of outreach warnings: `invalid`, `premium_rate`, `toll_free`, `shared_cost`, `uan`, `pager`, `not_sms_reachable` (landline). Empty for a clean, textable mobile. |
| `reason` | Short machine-readable status: `ok`, `possible-but-invalid`, `invalid-number`, `unparseable`, `empty-input`. |
| `validatedAt` | ISO timestamp of the check. |

***

### Honest note: this is offline metadata, **not** a live carrier/HLR lookup

`libphonenumber` validates a number against national numbering plans and derives
its type from metadata. It is accurate, instant and free — but it is **offline**.
That means:

- It tells you a number is **valid and, say, a mobile line** — it does **not**
  perform a **live carrier (HLR) lookup**, does not know the current **carrier**,
  and can't tell you whether the number was **ported** or is currently
  **active/switched-on**. Those need a paid, per-number network query.
- So we **do not invent carrier data.** Where a live tool would guess a carrier
  name, we give you the honest offline signals instead: `type`, `lineType`,
  `country`, and `countryCallingCode`. That's enough to normalize a list, split
  mobile from landline, and drop the invalid and premium/toll-free numbers —
  which is 90% of why you clean a list in the first place.

If you later need live carrier/active-line status on the *survivors*, run those
few numbers through a paid lookup — but only after this Actor has stripped out
the invalid ones for free.

***

### NumVerify / Twilio Lookup alternative

Services like **NumVerify** and **Twilio Lookup** charge per request and gate the
basic checks behind an API key and a metered account. For **format, validity,
country and line-type** — the checks most people actually run in bulk — you don't
need any of that. This Actor does the same offline validation `libphonenumber`
powers under the hood, on your whole list, with:

- **No API key** and no third-party signup.
- **Pay-per-result** pricing — you pay per number validated, nothing for idle time.
- **Bulk-first**: paste thousands of numbers, get one clean dataset (CSV / JSON /
  Excel / API).

Use it as your default bulk validator; reserve paid live-carrier lookups for the
handful of numbers where you genuinely need real-time active-line status.

***

### Input

```json
{
  "phoneNumbers": ["+14155552671", "+442071838750", "2025551234"],
  "defaultCountry": "US"
}
```

| Field | Type | Default | Description |
|---|---|---|---|
| `phoneNumbers` | array of strings | — (required) | Numbers to validate, one per line. E.164 numbers (with `+` and country code) parse as-is; national-format numbers use `defaultCountry`. |
| `defaultCountry` | string (ISO code) | `"US"` | Country used to interpret numbers written without a leading `+country` code. |

***

### Output

One record per number:

```json
{
  "input": "+14155552671",
  "isValid": true,
  "isPossible": true,
  "e164": "+14155552671",
  "national": "(415) 555-2671",
  "international": "+1 415 555 2671",
  "country": "US",
  "countryCallingCode": "1",
  "type": "FIXED_LINE_OR_MOBILE",
  "lineType": "Fixed line or mobile",
  "isMobile": true,
  "riskFlags": [],
  "reason": "ok",
  "validatedAt": "2026-07-04T00:00:00.000Z"
}
```

```json
{
  "input": "+442071838750",
  "isValid": true,
  "isPossible": true,
  "e164": "+442071838750",
  "national": "020 7183 8750",
  "international": "+44 20 7183 8750",
  "country": "GB",
  "countryCallingCode": "44",
  "type": "FIXED_LINE",
  "lineType": "Landline (fixed line)",
  "isMobile": false,
  "riskFlags": ["not_sms_reachable"],
  "reason": "ok",
  "validatedAt": "2026-07-04T00:00:00.000Z"
}
```

The dataset ships with a **table view** (input, valid, E.164, country, type,
mobile, risk flags) and exports to CSV, JSON, Excel, and via API.

***

### Use cases

- **Clean a lead list** before importing it into your CRM or dialer — one
  canonical E.164 column, invalids dropped.
- **Prep an SMS campaign** — keep `isMobile:true`, drop landlines (`not_sms_reachable`)
  so you don't pay to text a line that can't receive texts.
- **Verify signups** — validate the phone a user typed into your form, in the
  right country, before you store or SMS-verify it.
- **De-risk a purchased or scraped list** — strip `invalid`, `premium_rate` and
  `toll_free` numbers before they cost you.
- **Normalize mixed-country data** — turn a jumble of local formats into
  consistent E.164 your systems accept.
- **Segment by line type** — route mobiles to SMS, landlines to voice.

***

### FAQ

**Does it do a live carrier / HLR lookup or tell me the carrier name?**
No — see the honest note above. It's offline `libphonenumber` validation:
validity, E.164, country, and line type. It does **not** invent carrier data or
check whether a line is currently active/ported.

**What's the difference between `isValid` and `isPossible`?**
`isPossible` only checks the *length* is plausible for the country. `isValid`
also checks the *prefix* is a real allocation. Filter on `isValid`; use
`isPossible` to distinguish "typo" from "plausible but not a real number".

**What if a number has no `+` and no country code?**
It's interpreted using `defaultCountry` (default `US`). Set it to your list's
main country (e.g. `GB`, `DE`, `PE`) for accurate parsing of local-format numbers.

**Can it receive SMS if it's a landline?**
No — a `FIXED_LINE` can't receive SMS. Those get the `not_sms_reachable` flag so
you can exclude them from SMS sends.

**Do I need an API key or account anywhere?**
No. All numbering-plan metadata is bundled — nothing is queried over the network.

**How fast is it?**
Very — validation is pure and offline, so thousands of numbers run in seconds.

**Are results deduplicated?**
Yes, the input list is de-duplicated (whitespace-insensitive) before validation.

***

### Automate it

Run this Actor on a schedule or from your own code:

- **Schedule** it in the Apify Console to re-validate a list periodically.
- **API / SDK**: call it from Node or Python and read the dataset back.
- **Integrations**: pipe results to Google Sheets, Slack, Zapier, Make, or a
  webhook.
- **Chain it**: run **Website Contact Finder** → send the emails to **Bulk Email
  Verifier** and the phones to **this validator** → export the clean rows to your
  outreach tool.

Pricing is **pay-per-result**: you're charged per number validated, so a run
costs exactly what you use.

***

*Part of a lead-verification & enrichment suite: **Website Contact Finder** and
**Company Enrichment** to find and enrich; **Bulk Email Verifier** and **Bulk
Phone Number Validator** to clean each channel before you reach out.*

# Actor input Schema

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

One phone number per line. Numbers in E.164 form (with a leading + and country code, e.g. +14155552671) are parsed as-is. Numbers written in national/local form (e.g. 2025551234) are interpreted using the Default country below. Each number is returned as one clean record with E.164, national & international formats, line type, country and outreach risk flags. Fully offline — no API key.

## `defaultCountry` (type: `string`):

ISO 3166-1 alpha-2 country code (e.g. US, GB, DE, PE) used to interpret numbers written WITHOUT a leading +country code. Numbers already in E.164 form ignore this. Defaults to US.

## Actor input object example

```json
{
  "phoneNumbers": [
    "+14155552671",
    "+442071838750",
    "2025551234"
  ],
  "defaultCountry": "US"
}
```

# Actor output Schema

## `results` (type: `string`):

Rows produced by Phone Number Validator, one item per result in the default dataset.

# 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 = {
    "phoneNumbers": [
        "+14155552671",
        "+442071838750",
        "2025551234"
    ],
    "defaultCountry": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("renzomacar/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 = {
    "phoneNumbers": [
        "+14155552671",
        "+442071838750",
        "2025551234",
    ],
    "defaultCountry": "US",
}

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

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

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

```

## CLI example

```bash
echo '{
  "phoneNumbers": [
    "+14155552671",
    "+442071838750",
    "2025551234"
  ],
  "defaultCountry": "US"
}' |
apify call renzomacar/phone-validator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=renzomacar/phone-validator",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/FBqkupHQZuV7dtM1E/builds/CaFVvEzBHnhckIxe1/openapi.json
