Skip to content
On this page

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
Specification
YAML · JSON
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

HeaderTypeRequiredDescription
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.

FieldTypeRequiredDescription
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

A number in a common format
{
  "phone_number": "(702) 927-0000"
}
A number read in a country
{
  "phone_number": "020 7946 0000",
  "default_country": "GB"
}
Only accept numbers from some countries
{
  "phone_number": "+12025550100",
  "allowed_countries": [
    "US",
    "CA"
  ]
}
A test number, with a test key
{
  "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
HeaderTypeDescription
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
FieldTypeDescription
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

ValueMeaning
not_a_phone_numberEmpty, only punctuation, letters, an extension, or other characters
too_shortFewer digits than the country's numbers have
too_longMore digits than the country's numbers have
invalid_area_codeArea code not in service, or not for customer numbers
invalid_prefixExchange not valid or not assigned
not_in_serviceThousands block or exchange not in service
invalid_numberInternational number that is not valid or has no record
country_not_allowedOutside 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
A valid mobile number
{
  "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
}
An invalid number
{
  "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
}
A test-mode response
{
  "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 as phone_number over 64 characters, a country that is not an ISO 3166-1 alpha-2 code, an empty allowed_countries, a default_country outside allowed_countries, or an Idempotency-Key that 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
A field is missing or wrong
{
  "error": {
    "code": "invalid_request",
    "message": "phone_number is required and must be a string."
  }
}
The body is not JSON
{
  "error": {
    "code": "invalid_json",
    "message": "The request body is not valid JSON."
  }
}
A test key sent a number outside the test set
{
  "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
HeaderTypeDescription
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
No bearer token
{
  "error": {
    "code": "missing_key",
    "message": "Send your API key as Authorization: Bearer <key>."
  }
}
Unknown or revoked key
{
  "error": {
    "code": "invalid_key",
    "message": "This API key is not valid or was revoked."
  }
}
The key was in the query string
{
  "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
HeaderTypeDescription
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
HeaderTypeDescription
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
HeaderTypeDescription
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
HeaderTypeDescription
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

HeaderTypeRequiredDescription
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
HeaderTypeDescription
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
FieldTypeDescription
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
A live account with auto-refill
{
  "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
HeaderTypeDescription
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
No bearer token
{
  "error": {
    "code": "missing_key",
    "message": "Send your API key as Authorization: Bearer <key>."
  }
}
Unknown or revoked key
{
  "error": {
    "code": "invalid_key",
    "message": "This API key is not valid or was revoked."
  }
}
The key was in the query string
{
  "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
HeaderTypeDescription
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
HeaderTypeDescription
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:

NumberResult
+12025550100Valid, mobile, T-MOBILE, ported: false
+12025550101Valid, mobile, T-MOBILE, ported: true
+12025550102Valid, landline, VERIZON
+12025550103Valid, voip, BANDWIDTH.COM
+18005550100Valid, toll_free
+12025550104Valid, unknown line type
+1202555010Invalid, too_short
+11115550100Invalid, invalid_area_code
+12025550106Invalid, not_in_service
not a numberInvalid, not_a_phone_number
+12025550190402 insufficient_funds
+12025550191403 account_on_hold
+12025550192429 rate_limited
+12025550193503 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.

CodeHTTP statusExample 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.

Accessibility