Phone Number Validator API - E.164, Line Type & Risk Flags
Pricing
Pay per usage
Phone Number Validator API - E.164, Line Type & Risk Flags
Validate and normalize phone numbers. Returns E.164, national/international format, country, calling code, line type, tel URI, and risk flags. Invalid rows are free.
Pricing
Pay per usage
Rating
0.0
(0)
Developer
George Kioko
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
1
Monthly active users
6 days ago
Last modified
Categories
Share
Validate and normalize phone numbers at scale. Give it a single number or a list of up to 100, and get back clean E.164, the country, the international and national formats, the line type, a tel: URI, and deterministic risk flags. Invalid and unparsable rows are returned for free; only valid keepers are billed.
Validation is fully offline and deterministic, backed by Google's libphonenumber metadata through libphonenumber-js. No accounts, no keys, no scraping.
How it works
flowchart LRA[Standby API call<br/>or batch input] --> B[Normalize input<br/>strip spaces, parse with<br/>defaultCountry hint]B --> C[Validate with<br/>libphonenumber-js]C --> D{Valid?}D -- no --> E[Return row<br/>valid=false + reason<br/>FREE, not charged]D -- yes --> F[Derive E.164, formats,<br/>country, line type,<br/>risk flags]F --> G[Actor.charge<br/>phone-number-validated]E --> H[Dataset row<br/>or JSON response]G --> H
sequenceDiagramparticipant Clientparticipant Standby as Standby URLparticipant Actorparticipant Lib as libphonenumber-jsClient->>Standby: GET /lookup?phoneNumber=...Standby->>Actor: route requestActor->>Lib: parse + validateLib-->>Actor: valid / line type / formatsalt number is validActor->>Actor: Actor.charge("phone-number-validated")else invalid or unparsableNote over Actor: no charge (free)endActor-->>Client: JSON result
What it does
- Normalizes any input to E.164 (
+12133734253) when the number is valid - Returns national and international display formats
- Detects country (ISO 3166 alpha-2) and country calling code
- Reports line type when the metadata supports it (
MOBILE,FIXED_LINE,TOLL_FREE,PREMIUM_RATE,VOIP, ...) - Emits a
tel:URI for click-to-call - Adds deterministic risk flags inferred from validity, possibility, and line type
- Cleans bulk lead lists: invalid and unparsable rows are returned for free
What it does NOT do (yet)
This is honest about its scope. v1 is a validation and normalization API. It does not:
- Perform paid HLR / network carrier lookups
- Scrape Truecaller or any consumer caller-ID app
- Return the real carrier name or the number's timezone
For that reason carrier and timezone are always returned as null, each with an explicit status field (carrierLookupStatus: "not_configured", timezoneLookupStatus: "not_available_offline"). The output shape is stable, so a future optional paid-provider integration can fill those fields without breaking your code. We will not fake carrier data.
Modes
1. Standby API (real-time)
Leave the input empty and call the Actor as an HTTP service.
| Method | Path | Description |
|---|---|---|
| GET | / or /health | Service metadata and endpoint list |
| GET | /lookup?phoneNumber=...&defaultCountry=US | Validate one number |
| POST | /lookup | Validate one number (JSON body) |
| POST | /lookup-batch | Validate up to 100 numbers (JSON body) |
CORS is enabled and every response is JSON. 400 for bad or missing input, 404 for unknown routes, 500 for unexpected errors.
Example: GET single lookup
$curl "https://<your-standby-url>/lookup?phoneNumber=%2B447400123456"
Example: POST single lookup
curl -X POST https://<your-standby-url>/lookup \-H "Content-Type: application/json" \-d '{"phoneNumber":"(213) 373-4253","defaultCountry":"US","includeRiskSignals":true}'
Example: POST batch (max 100)
curl -X POST https://<your-standby-url>/lookup-batch \-H "Content-Type: application/json" \-d '{"phoneNumbers":["+12133734253","07400 123456","not a phone"],"defaultCountry":"GB"}'
2. Batch Actor
Run it with input. Provide either a single phoneNumber or a phoneNumbers array (up to 100). One dataset row is pushed per number, in input order. Export as JSON, CSV, or Excel from the dataset.
{"phoneNumbers": ["+12133734253", "07400 123456", "not a phone number"],"defaultCountry": "GB","includeRiskSignals": true}
Input
| Field | Type | Default | Description |
|---|---|---|---|
phoneNumber | string | +12133734253 | Single number. E.164 or national format with a defaultCountry hint. |
phoneNumbers | string[] | none | Bulk list, max 100. Takes precedence over phoneNumber. |
defaultCountry | string | US | ISO 3166-1 alpha-2 code used to parse national-format numbers. Ignored for +E.164 input. |
includeRiskSignals | boolean | true | Attach risk flags to each result. |
Output
One object per number:
{"input": "07400 123456","valid": true,"isPossible": true,"e164": "+447400123456","nationalFormat": "07400 123456","internationalFormat": "+44 7400 123456","uri": "tel:+447400123456","countryCode": "GB","countryCallingCode": "44","lineType": "MOBILE","carrier": null,"carrierLookupStatus": "not_configured","timezone": null,"timezoneLookupStatus": "not_available_offline","riskFlags": [],"riskScore": 0,"confidence": "high","reason": "Valid phone number","error": null}
Risk flags
Risk flags are derived only from validation facts, so they are reproducible:
| Flag | Meaning |
|---|---|
invalid | The number is not valid for any region. |
not_possible | The length or pattern can't be a real number. |
possible_not_valid | Right length, but not a valid number for any region. |
unparsable | Could not be parsed at all (empty or non-numeric). |
premium_rate | Premium-rate line type (PREMIUM_RATE). |
voip | VoIP line type. |
pager | Pager line type. |
shared_cost | Shared-cost line type. |
confidence is high for valid numbers, medium for possible-but-invalid, and low for not-possible or unparsable.
Pricing
Pay per event:
- Current Apify scheduled price:
phone-number-validated- $0.003 per valid number returned. - Recommended next price after Apify's pricing cooldown: $0.004 per valid number returned.
Only valid numbers are billed. Invalid, unparsable, and possible-but-invalid rows are returned for free, so cleaning a noisy lead list never costs more than the keepers it produces.
Apify currently blocks another pricing modification until 2026-07-16 because this Actor's first pricing record was just created. The table below shows the recommended next price once the cooldown allows it.
| Valid numbers | Cost at $0.004 |
|---|---|
| 100 | $0.40 |
| 1,000 | $4.00 |
| 10,000 | $40.00 |
A batch of 100 with 80 valid numbers costs $0.32 (the 20 bad rows are free).
Competitor price positioning
Surveyed against the live Apify Store on 2026-06-17. The honest comparison set is split into two groups: cheap offline libphonenumber validators and higher-priced phone-intelligence APIs that add HLR, carrier, owner, fraud, or messaging signals. This Actor is a clean validation/normalization API, so the target price stays below the intelligence tier and below EasyAPI while charging only for valid rows.
| Actor | Per-number price | What you get |
|---|---|---|
| khadinakbar/phone-number-lookup-api | $0.025 | Phone intelligence: HLR carrier, owner/CNAM, fraud score, breach data, WhatsApp/Telegram |
| easyapi/phone-number-validation | ~$0.00499 ($4.99 per 1,000) | Validation, parse, type, location, formats |
| george.the.developer/phone-number-validator | current $0.003; target $0.004 valid only | E.164, formats, country, line type, deterministic risk flags. Invalid rows free. |
| zhorex/phone-number-validator | ~$0.002 ($2.00 per 1,000) | Offline libphonenumber validation, timezone/location, carrier where metadata supports it |
| junipr/phone-number-validator | ~$0.0013 ($1.30 per 1,000) | Validation, formats, line type, region, carrier/location claims |
Why $0.004, not $0.0075:
- $0.0075 would be too high for v1 because this Actor does not return live HLR, real carrier, owner, fraud, SIM, WhatsApp, or Telegram data.
- $0.004 is still a 33% lift over the current $0.003 price while staying below EasyAPI and far below phone-intelligence APIs.
- On a noisy list with 65% valid numbers, $0.004 per valid result works out to about $2.60 per 1,000 input rows because the invalid 35% is free.
- The premium over cheaper offline peers is justified by valid-only billing, a real-time Standby API, batch mode, and deterministic risk flags.
Why pay more than the cheapest offline validator:
- You pay only for valid rows, not every input row.
- You can call it as a real-time HTTP Standby API or as a batch Actor.
- The output is honest: no fake carrier, timezone, HLR, or owner data.
- Risk flags are deterministic and reproducible for lead-list cleaning.
flowchart LRsubgraph INTEL["Intelligence tier: $0.015 to $0.025"]A1[HLR carrier / owner<br/>fraud / messaging signals]endsubgraph OURS["This actor: target $0.004"]B1[Offline validation<br/>valid-only billing<br/>Standby API + risk flags]endsubgraph OFFLINE["Offline peers: $0.0013 to $0.002"]C1[Per-result validation<br/>usually all rows billed]endA1 -. richer data, higher price .-> B1B1 -. pay only for keepers .-> C1
Notes on line type and country
lineType comes from the bundled metadata and may be null for some countries even when the number is valid. Country detection can also be null for numbers that are possible but not assigned to a specific region. Both are reported honestly rather than guessed.
Limitations
- No real carrier name or timezone in v1 (see "What it does NOT do").
- Line type availability varies by country.
- Validation reflects numbering-plan rules, not whether a number is currently active on a network. That requires a paid HLR lookup, which is out of scope for v1.