Phone Validator API v5
Validate phone numbers and identify their line type, carrier and location.
- Base URL
https://api.phonevalidator.com- Version
- 5.0.0
- This page
- Markdown
Look up a phone number with POST /v5/lookups. The response states whether the number is valid and gives its country, region, line type, carrier and port status.
Authentication
Send your API key as a bearer token:
Authorization: Bearer <api key>
Live keys start with pv_live_ and test keys with pv_test_ (see Test mode).
Errors
Errors return a non-2xx status and a body of the form {"error": {"code": "...", "message": "..."}}. Match on code; message is for people. Every response has a Request-Id header for support.
Rate limits
Each account may make 25 requests per second across all of its keys. Higher limits are available on request. RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset report the current window; a request over the limit receives 429 rate_limited with Retry-After.
Versioning
v5 changes only by addition. New fields and new line_type and invalid_reason values may appear: ignore unknown fields, treat an unknown line_type as unknown and an unknown invalid_reason as invalid. Existing fields keep their name, type and meaning.
Look up a phone number
POST https://api.phonevalidator.com/v5/lookups
Validates a phone number and returns its details. An invalid number returns 200 with valid: false and an invalid_reason.
Spaces, (), - and . are ignored, and a leading + or 00 marks an international number. Other numbers are read in default_country, else in the first of allowed_countries, else as a North American number and then as an international one.
With an Idempotency-Key, the first successful response is stored for 24 hours for your API key. The same key and body return the stored response with Idempotent-Replayed: true, without a second charge. The same key returns 409 idempotency_in_progress while the first request is running and 422 idempotency_key_reused with a different body. Errors are not stored, so a failed request can be retried with the same key.
Request headers
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | required |
Bearer <api key>. Your API key.
|
Content-Type |
string | required |
application/json
|
Idempotency-Key |
string | optional |
A unique value per lookup, such as a UUID.
Maximum 255 characters · Minimum 1 character · Example 4c6f8e0a-1b7d-4f5e-9a3c-2d8e7b6a5f10
|
Request body
Content type application/json.
| Field | Type | Required | Description |
|---|---|---|---|
phone_number |
string | required | The phone number, in any common format. Maximum 64 characters |
default_country |
string | optional |
The country used to read a number written without + or 00, as an ISO 3166-1 alpha-2 code such as GB. Numbers with + are read as written. Must be in allowed_countries when both are sent.
Pattern ^[A-Z]{2}$
|
allowed_countries |
string[] | optional |
The countries to accept, as ISO codes such as ["US","CA"]. A number from any other country returns valid: false with invalid_reason: "country_not_allowed". Without default_country, the first country is used to read numbers written without +.
At least 1 item
|
Examples
{
"phone_number": "(702) 927-0000"
}
{
"phone_number": "020 7946 0000",
"default_country": "GB"
}
{
"phone_number": "+12025550100",
"allowed_countries": [
"US",
"CA"
]
}
{
"phone_number": "+12025550101"
}
Code samples
curl https://api.phonevalidator.com/v5/lookups \
-H "Authorization: Bearer $PHONEVALIDATOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone_number":"(702) 927-0000"}'
import os
import requests
response = requests.post(
"https://api.phonevalidator.com/v5/lookups",
headers={"Authorization": f"Bearer {os.environ['PHONEVALIDATOR_API_KEY']}"},
json={"phone_number": "(702) 927-0000"},
timeout=30,
)
print(response.status_code, response.json())
const response = await fetch("https://api.phonevalidator.com/v5/lookups", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PHONEVALIDATOR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"phone_number":"(702) 927-0000"}),
});
console.log(response.status, await response.json());
<?php
$curl = curl_init("https://api.phonevalidator.com/v5/lookups");
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("PHONEVALIDATOR_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode(['phone_number' => '(702) 927-0000']),
]);
$response = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
echo $status, " ", $response, "\n";
require "json"
require "net/http"
uri = URI("https://api.phonevalidator.com/v5/lookups")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("PHONEVALIDATOR_API_KEY")}"
request["Content-Type"] = "application/json"
request.body = JSON.generate({ "phone_number" => "(702) 927-0000" })
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
puts response.code, response.body
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
req, err := http.NewRequest("POST", "https://api.phonevalidator.com/v5/lookups", strings.NewReader(`{"phone_number":"(702) 927-0000"}`))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("PHONEVALIDATOR_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode, string(data))
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("PHONEVALIDATOR_API_KEY"));
var content = new StringContent("""{"phone_number":"(702) 927-0000"}""", Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://api.phonevalidator.com/v5/lookups", content);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");
Responses
200 OK
The lookup result.
Response headers
| Header | Type | Description |
|---|---|---|
Request-Id |
string | Identifies this request. Quote it to support. |
RateLimit-Limit |
integer | Requests allowed per second. |
RateLimit-Remaining |
integer | Requests left in the current second. |
RateLimit-Reset |
integer | Seconds until the limit refills. |
Cache-Control |
string | Always no-store. |
Idempotent-Replayed |
string | true when the response is the stored response for this Idempotency-Key. Absent otherwise. |
Response fields
| Field | Type | Description |
|---|---|---|
phone_number |
string | null |
The number in E.164 format. null when the input cannot be read as a phone number.
Pattern ^\+[1-9]\d{1,14}$
|
valid |
boolean |
true when the number is in an assigned, in-service range of its numbering plan.
|
invalid_reason |
string | null |
Why the number is invalid; null when it is valid. New values may be added; treat an unknown value as invalid.
Details below.
One of not_a_phone_number, too_short, too_long, invalid_area_code, invalid_prefix, not_in_service, invalid_number, country_not_allowed
|
format_national |
string | null |
National display format. null for an invalid number.
|
format_international |
string | null |
International display format. null for an invalid number.
|
country |
string | null |
ISO 3166-1 alpha-2 code. US territories have their own codes (PR, GU, VI, AS, MP).
Pattern ^[A-Z]{2}$
|
region |
string | null |
US state or Canadian province code, such as NV or ON.
|
city |
string | null | The locality where the number was issued. |
time_zone |
string | null |
IANA time zone where the number was issued. null when the area spans more than one zone.
|
line_type |
string | null |
mobile, landline, voip, toll_free or unknown; null for an invalid number. New values may be added; treat an unknown value as unknown.
Details below.
One of mobile, landline, voip, toll_free, unknown
|
carrier |
string | null |
The number's current carrier, or the carrier its number block is assigned to. For a number on a wholesale VoIP network, the company behind it, such as GOOGLE VOICE.
|
ported |
boolean | null |
Whether the number has been ported to another carrier. null where port data is not available, including Canada.
|
cost |
string |
Amount billed for this request, in USD.
Pattern ^\d+\.\d{4}$
|
balance |
string |
Available balance after this request, in USD. GET /v5/account returns the exact balance while other lookups are in progress.
Pattern ^\d+\.\d{4}$
|
test_mode |
boolean |
true for a request made with a test key.
|
invalid_reason
| Value | Meaning |
|---|---|
not_a_phone_number | Empty, only punctuation, letters, an extension, or other characters |
too_short | Fewer digits than the country's numbers have |
too_long | More digits than the country's numbers have |
invalid_area_code | Area code not in service, or not for customer numbers |
invalid_prefix | Exchange not valid or not assigned |
not_in_service | Thousands block or exchange not in service |
invalid_number | International number that is not valid or has no record |
country_not_allowed | Outside the request's allowed_countries |
line_type
voip: served by a VoIP or wholesale carrier, including app numbers such as Google Voice.unknown: the line could not be classified, as with some premium-rate and pager numbers.
Examples
{
"phone_number": "+17029270000",
"valid": true,
"invalid_reason": null,
"format_national": "(702) 927-0000",
"format_international": "+1 702-927-0000",
"country": "US",
"region": "NV",
"city": "LAS VEGAS",
"time_zone": "America/Los_Angeles",
"line_type": "mobile",
"carrier": "T-MOBILE",
"ported": true,
"cost": "0.0040",
"balance": "41.2760",
"test_mode": false
}
{
"phone_number": "+11115550100",
"valid": false,
"invalid_reason": "invalid_area_code",
"format_national": null,
"format_international": null,
"country": "US",
"region": null,
"city": null,
"time_zone": null,
"line_type": null,
"carrier": null,
"ported": null,
"cost": "0.0040",
"balance": "41.2720",
"test_mode": false
}
{
"phone_number": "+12025550101",
"valid": true,
"invalid_reason": null,
"format_national": "(202) 555-0101",
"format_international": "+1 202-555-0101",
"country": "US",
"region": "DC",
"city": "WASHINGTON",
"time_zone": "America/New_York",
"line_type": "mobile",
"carrier": "T-MOBILE",
"ported": true,
"cost": "0.0000",
"balance": "41.2760",
"test_mode": true
}
400 Bad Request
The request is malformed.
invalid_request: a field is missing, unknown or out of range, such asphone_numberover 64 characters, a country that is not an ISO 3166-1 alpha-2 code, an emptyallowed_countries, adefault_countryoutsideallowed_countries, or anIdempotency-Keythat is blank or over 255 characters.invalid_json: the body is not a JSON object.test_mode_number: a test key sent a number that is not a test number.
Carries the common headers of the 200 response.
Examples
{
"error": {
"code": "invalid_request",
"message": "phone_number is required and must be a string."
}
}
{
"error": {
"code": "invalid_json",
"message": "The request body is not valid JSON."
}
}
{
"error": {
"code": "test_mode_number",
"message": "Test keys accept only the documented test numbers."
}
}
401 Unauthorized
The API key is missing, invalid or revoked, or was sent in the URL.
Response headers
| Header | Type | Description |
|---|---|---|
WWW-Authenticate |
string | The authentication scheme, Bearer. Sent when the key is missing or not valid. |
The common headers of the 200 response are present too.
Examples
{
"error": {
"code": "missing_key",
"message": "Send your API key as Authorization: Bearer <key>."
}
}
{
"error": {
"code": "invalid_key",
"message": "This API key is not valid or was revoked."
}
}
{
"error": {
"code": "key_in_url",
"message": "Send the API key in the Authorization header."
}
}
402 Payment Required
The balance does not cover the lookup. Not billed.
Carries the common headers of the 200 response.
Example
{
"error": {
"code": "insufficient_funds",
"message": "This lookup costs $0.0040; $0.0012 is available."
}
}
403 Forbidden
The account cannot make requests: account_inactive, api_disabled (API access is off for the account) or account_on_hold (contact support).
Carries the common headers of the 200 response.
Examples
{
"error": {
"code": "account_inactive",
"message": "This account is not active."
}
}
{
"error": {
"code": "api_disabled",
"message": "API access is turned off for this account."
}
}
{
"error": {
"code": "account_on_hold",
"message": "This account is on hold. Contact support."
}
}
405 Method Not Allowed
Method not allowed. Lookups use POST.
Response headers
| Header | Type | Description |
|---|---|---|
Allow |
string | The method this endpoint accepts. |
The common headers of the 200 response are present too.
Example
{
"error": {
"code": "method_not_allowed",
"message": "Use POST for this endpoint."
}
}
409 Conflict
A request with this Idempotency-Key is in progress. Retry with the same key after Retry-After.
Response headers
| Header | Type | Description |
|---|---|---|
Retry-After |
integer | Seconds to wait before retrying. |
The common headers of the 200 response are present too.
Example
{
"error": {
"code": "idempotency_in_progress",
"message": "A request with this Idempotency-Key is still running."
}
}
415 Unsupported Media Type
The body must be JSON, sent with Content-Type: application/json.
Carries the common headers of the 200 response.
Example
{
"error": {
"code": "unsupported_media_type",
"message": "Send a JSON body with Content-Type: application/json."
}
}
422 Unprocessable Content
This Idempotency-Key was used with a different request body.
Carries the common headers of the 200 response.
Example
{
"error": {
"code": "idempotency_key_reused",
"message": "This Idempotency-Key was used with a different request."
}
}
429 Too Many Requests
The account's rate limit is exceeded. Not billed; retry after Retry-After.
Response headers
| Header | Type | Description |
|---|---|---|
Retry-After |
integer | Seconds to wait before retrying. |
The common headers of the 200 response are present too.
Example
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after the Retry-After seconds."
}
}
500 Internal Server Error
An internal error. Retry with the same Idempotency-Key; a request that was billed before the error is not billed again.
Carries the common headers of the 200 response.
Example
{
"error": {
"code": "internal_error",
"message": "Internal error. Retry with the same Idempotency-Key."
}
}
503 Service Unavailable
Carrier data is temporarily unavailable. Not billed; retry after Retry-After.
Response headers
| Header | Type | Description |
|---|---|---|
Retry-After |
integer | Seconds to wait before retrying. |
The common headers of the 200 response are present too.
Example
{
"error": {
"code": "lookup_unavailable",
"message": "The carrier lookup is unavailable; retry shortly."
}
}
Read the account
GET https://api.phonevalidator.com/v5/account
Returns the account's balance, price, rate limit, auto-refill and balance-alert settings, and usage for the current month. Settings are managed in the dashboard. Not billed; counts toward the rate limit.
Request headers
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | required |
Bearer <api key>. Your API key.
|
Code samples
curl https://api.phonevalidator.com/v5/account \
-H "Authorization: Bearer $PHONEVALIDATOR_API_KEY"
import os
import requests
response = requests.get(
"https://api.phonevalidator.com/v5/account",
headers={"Authorization": f"Bearer {os.environ['PHONEVALIDATOR_API_KEY']}"},
timeout=30,
)
print(response.status_code, response.json())
const response = await fetch("https://api.phonevalidator.com/v5/account", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.PHONEVALIDATOR_API_KEY}`,
},
});
console.log(response.status, await response.json());
<?php
$curl = curl_init("https://api.phonevalidator.com/v5/account");
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("PHONEVALIDATOR_API_KEY"),
],
]);
$response = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
echo $status, " ", $response, "\n";
require "json"
require "net/http"
uri = URI("https://api.phonevalidator.com/v5/account")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("PHONEVALIDATOR_API_KEY")}"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
puts response.code, response.body
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("GET", "https://api.phonevalidator.com/v5/account", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("PHONEVALIDATOR_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode, string(data))
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("PHONEVALIDATOR_API_KEY"));
var response = await client.GetAsync("https://api.phonevalidator.com/v5/account");
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");
Responses
200 OK
The account.
Response headers
| Header | Type | Description |
|---|---|---|
Request-Id |
string | Identifies this request. Quote it to support. |
RateLimit-Limit |
integer | Requests allowed per second. |
RateLimit-Remaining |
integer | Requests left in the current second. |
RateLimit-Reset |
integer | Seconds until the limit refills. |
Cache-Control |
string | Always no-store. |
Response fields
| Field | Type | Description |
|---|---|---|
balance |
string |
Available balance in USD, including lookups in progress.
Pattern ^\d+\.\d{4}$
|
price_per_lookup |
string |
Price of one lookup for this account, in USD.
Pattern ^\d+\.\d{4}$
|
trial_lookups_remaining |
integer | Free lookups remaining. Minimum 0 |
rate_limit_per_second |
integer | Requests per second allowed for the account. Minimum 1 |
auto_refill_enabled |
boolean | Whether auto-refill is on. |
auto_refill_threshold |
string |
Balance that triggers auto-refill, in USD.
Pattern ^\d+\.\d{4}$
|
auto_refill_amount |
string |
Amount added by auto-refill, in USD.
Pattern ^\d+\.\d{4}$
|
balance_alert_enabled |
boolean | Whether the low-balance email is on. |
balance_alert_threshold |
string |
Balance that triggers the low-balance email, in USD.
Pattern ^\d+\.\d{4}$
|
lookups_this_month |
integer | Billed lookups in the current UTC calendar month. Minimum 0 |
spent_this_month |
string |
Amount billed in the current UTC calendar month, in USD.
Pattern ^\d+\.\d{4}$
|
test_mode |
boolean |
true for a request made with a test key.
|
Example
{
"balance": "41.2760",
"price_per_lookup": "0.0040",
"trial_lookups_remaining": 0,
"rate_limit_per_second": 25,
"auto_refill_enabled": true,
"auto_refill_threshold": "5.0000",
"auto_refill_amount": "25.0000",
"balance_alert_enabled": false,
"balance_alert_threshold": "5.0000",
"lookups_this_month": 18234,
"spent_this_month": "72.9360",
"test_mode": false
}
401 Unauthorized
The API key is missing, invalid or revoked, or was sent in the URL.
Response headers
| Header | Type | Description |
|---|---|---|
WWW-Authenticate |
string | The authentication scheme, Bearer. Sent when the key is missing or not valid. |
The common headers of the 200 response are present too.
Examples
{
"error": {
"code": "missing_key",
"message": "Send your API key as Authorization: Bearer <key>."
}
}
{
"error": {
"code": "invalid_key",
"message": "This API key is not valid or was revoked."
}
}
{
"error": {
"code": "key_in_url",
"message": "Send the API key in the Authorization header."
}
}
403 Forbidden
The account cannot make requests: account_inactive, api_disabled (API access is off for the account) or account_on_hold (contact support).
Carries the common headers of the 200 response.
Examples
{
"error": {
"code": "account_inactive",
"message": "This account is not active."
}
}
{
"error": {
"code": "api_disabled",
"message": "API access is turned off for this account."
}
}
{
"error": {
"code": "account_on_hold",
"message": "This account is on hold. Contact support."
}
}
405 Method Not Allowed
Method not allowed. The account uses GET.
Response headers
| Header | Type | Description |
|---|---|---|
Allow |
string | The method this endpoint accepts. |
The common headers of the 200 response are present too.
Example
{
"error": {
"code": "method_not_allowed",
"message": "Use GET for this endpoint."
}
}
429 Too Many Requests
The account's rate limit is exceeded. Not billed; retry after Retry-After.
Response headers
| Header | Type | Description |
|---|---|---|
Retry-After |
integer | Seconds to wait before retrying. |
The common headers of the 200 response are present too.
Example
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after the Retry-After seconds."
}
}
500 Internal Server Error
An internal error. Retry with the same Idempotency-Key; a request that was billed before the error is not billed again.
Carries the common headers of the 200 response.
Example
{
"error": {
"code": "internal_error",
"message": "Internal error. Retry with the same Idempotency-Key."
}
}
Test mode
Test keys (pv_test_...) return fixed responses for the numbers below and are never billed. Any other number returns 400 test_mode_number. Test responses set test_mode: true and count toward the account's rate limit.
The numbers are in the reserved 555-01XX range:
| Number | Result |
|---|---|
+12025550100 | Valid, mobile, T-MOBILE, ported: false |
+12025550101 | Valid, mobile, T-MOBILE, ported: true |
+12025550102 | Valid, landline, VERIZON |
+12025550103 | Valid, voip, BANDWIDTH.COM |
+18005550100 | Valid, toll_free |
+12025550104 | Valid, unknown line type |
+1202555010 | Invalid, too_short |
+11115550100 | Invalid, invalid_area_code |
+12025550106 | Invalid, not_in_service |
not a number | Invalid, not_a_phone_number |
+12025550190 | 402 insufficient_funds |
+12025550191 | 403 account_on_hold |
+12025550192 | 429 rate_limited |
+12025550193 | 503 lookup_unavailable |
Valid test numbers report city: "WASHINGTON" and region: "DC" (null for the toll-free number).
Error codes
Each error code with its HTTP status.
| Code | HTTP status | Example message |
|---|---|---|
invalid_json |
400 | The request body is not valid JSON. |
invalid_request |
400 | phone_number is required and must be a string. |
test_mode_number |
400 | Test keys accept only the documented test numbers. |
invalid_key |
401 | This API key is not valid or was revoked. |
key_in_url |
401 | Send the API key in the Authorization header. |
missing_key |
401 | Send your API key as Authorization: Bearer <key>. |
insufficient_funds |
402 | This lookup costs $0.0040; $0.0012 is available. |
account_inactive |
403 | This account is not active. |
account_on_hold |
403 | This account is on hold. Contact support. |
api_disabled |
403 | API access is turned off for this account. |
not_found |
404 | No such endpoint. |
method_not_allowed |
405 | Use POST for this endpoint. |
idempotency_in_progress |
409 | A request with this Idempotency-Key is still running. |
unsupported_media_type |
415 | Send a JSON body with Content-Type: application/json. |
idempotency_key_reused |
422 | This Idempotency-Key was used with a different request. |
rate_limited |
429 | Too many requests. Retry after the Retry-After seconds. |
internal_error |
500 | Internal error. Retry with the same Idempotency-Key. |
lookup_unavailable |
503 | The carrier lookup is unavailable; retry shortly. |
Looking for older API docs? Version 4 and earlier.