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

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

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

## Pricing

from $1.50 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 — Bulk, International, Line Type

Validate and normalize any list of phone numbers — mixed countries, messy formats — into valid/invalid, canonical **E.164**, country, and **line type** (mobile / landline / VoIP / toll-free / premium-rate), with outreach risk flags. For SMS campaign prep, CRM hygiene and call-center list cleaning.
**Pricing: pay per number validated** (current rate on the Pricing tab of the Actor page). Runs Google's `libphonenumber` engine entirely offline — no API key, no per-lookup service.

### 🎥 Video walkthrough

https://youtu.be/sViTqFM37lQ

### Start here: three jobs this does in one pass

1. **Kill the numbers that waste money.** Invalid numbers your SMS platform still charges you to attempt; premium-rate lines that rack up charges; toll-free IVRs that aren't people. All flagged in `riskFlags`.
2. **Split mobile from landline.** A fixed line cannot receive a text — `isMobile` and `not_sms_reachable` let you build an SMS list that actually delivers.
3. **Normalize everything to E.164.** `+14155552671` is the one format SMS APIs, CRMs and dialers universally accept. Store that column and your routing problems disappear.

### Bulk international phone number validator with line type detection

The engine is Google's `libphonenumber` numbering-plan metadata, bundled and run offline — the same library behind most paid lookup APIs' basic layer. That makes this Actor:

- **International by default** — every country's numbering plan; local-format numbers are interpreted via your `defaultCountry`.
- **Fast** — no network call per number; thousands of numbers validate in seconds.
- **Keyless** — nothing to sign up for, unlike NumVerify or Twilio Lookup, which meter the same basic checks per request behind an API key.

Long-tail jobs: validate phone numbers in bulk free of API keys, check if a number is mobile or landline before texting, convert a phone list to E.164, detect premium-rate numbers in a purchased list.

#### Where a paid lookup still wins

`libphonenumber` is offline metadata. It proves a number is valid and typed (e.g. mobile in GB) — it does **not** do a live carrier/HLR query, so it can't name the current carrier or confirm the line is active or ported. This Actor doesn't invent that data. The economical pattern: clean the whole list here at the per-number rate, then run a paid HLR lookup only on the survivors where live status truly matters.

### Real input → real output

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

```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"
}
```

`input` echoes your original string so you can join results back to source rows. The dataset has a ready table view and exports to CSV, JSON, Excel or the API.

#### Field guide

| Field | Meaning |
|---|---|
| `isValid` | Right length **and** an allocated prefix under the country's numbering plan. Filter on this. |
| `isPossible` | Length is plausible only — separates "typo" from "looks real but unallocated". |
| `e164` / `national` / `international` | The three formats; store `e164`. |
| `country` / `countryCallingCode` | ISO code (`US`, `GB`, `DE`…) and dialing prefix. |
| `type` / `lineType` | `MOBILE`, `FIXED_LINE`, `FIXED_LINE_OR_MOBILE`, `VOIP`, `TOLL_FREE`, `PREMIUM_RATE`, `SHARED_COST`, `UAN`, `PAGER` — plus a plain-English label. |
| `isMobile` | SMS candidate (mobile or the ambiguous fixed-or-mobile). |
| `riskFlags` | `invalid`, `premium_rate`, `toll_free`, `shared_cost`, `uan`, `pager`, `not_sms_reachable`. Empty = clean, textable. |
| `reason` | `ok`, `possible-but-invalid`, `invalid-number`, `unparseable`, `empty-input`. |

### FAQ — what buyers ask before running a list

**Can it tell me if a phone number is mobile or landline?**
Yes — that's the `type`/`lineType`/`isMobile` trio, derived from the country's numbering plan. Landlines additionally get `not_sms_reachable` so SMS lists exclude them automatically.

**Does it work for international numbers, or US only?**
Every country. Numbers with `+`country code parse as-is; local-format numbers are interpreted with `defaultCountry` (set it to `GB`, `DE`, `PE`, etc. to match your list).

**Will it tell me the carrier or whether the line is active?**
No — that requires a live HLR query, which this offline engine can't and won't fake. You get validity, country, format and line type: enough for 90% of list-cleaning. Run paid lookups only on the cleaned survivors.

**What's `isValid` vs `isPossible`?**
`isPossible` checks length only; `isValid` also checks the prefix is actually allocated. A row with `isPossible: true, isValid: false` looks real but isn't a working allocation.

**How much does cleaning 10,000 numbers cost?**
You pay a per-number fee for each number validated (see the Pricing tab of the Actor page for the current rate), and it finishes in well under a minute since validation is offline. No idle-time compute charges.

**Do duplicates get billed twice?**
No — the input list is de-duplicated (whitespace-insensitive) before validation.

**Can I run it automatically when new leads arrive?**
Yes: schedule it in Apify Console or call it from the API/SDK (Node, Python), then pipe results to Google Sheets, Zapier, Make or a webhook.

**What do I run on the email column of the same list?**
The sibling [Bulk Email Verifier](https://apify.com/renzomacar/email-verifier) — same keyless, pay-per-result approach for emails (MX, disposable, role checks). Contacts often come from the [Website Contact Finder](https://apify.com/renzomacar/website-contact-finder) upstream.

### Typical workflows

- **SMS campaign prep** — keep `isMobile: true`, drop `not_sms_reachable`, export E.164.
- **CRM import hygiene** — one canonical phone column, invalids and premium-rate stripped.
- **Form validation at scale** — batch-validate user-typed numbers in the right country.
- **Purchased-list triage** — measure list quality instantly by the share of `isValid` rows.
- **Routing** — mobiles to SMS, landlines to the dialer, VoIP flagged for review.

# 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(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 '{
  "phoneNumbers": [
    "+14155552671",
    "+442071838750",
    "2025551234"
  ],
  "defaultCountry": "US"
}' |
apify call renzomacar/phone-validator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,renzomacar/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/FBqkupHQZuV7dtM1E/builds/MNpzFqZldi0dv1Kbs/openapi.json
