Code examples
Copy-paste snippets for the 5 most common languages. They all hit the same endpoint with the same input — pick whichever stack you're using.
cURL
curl https://phonevalidationapi.com/api/v1/validate \
-H "Authorization: Bearer pvs_live_..." \
-H "Content-Type: application/json" \
-d '{"phone":"+33612345678"}'
PHP
<?php
$ch = curl_init('https://phonevalidationapi.com/api/v1/validate');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('PVS_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['phone' => '+33612345678']),
CURLOPT_TIMEOUT => 10,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$data = json_decode($body, true);
if ($status === 200 && $data['valid'] && $data['confidence'] !== 'low') {
echo "OK: {$data['formatted']['e164']} ({$data['line_type']}, {$data['carrier']})\n";
} else {
echo "REJECT: {$data['reason']}\n";
}
Python (requests)
import os
import requests
API_KEY = os.environ["PVS_API_KEY"]
r = requests.post(
"https://phonevalidationapi.com/api/v1/validate",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"phone": "+33612345678"},
timeout=10,
)
data = r.json()
if r.ok and data["valid"] and data["confidence"] != "low":
print(f"OK: {data['formatted']['e164']} ({data['line_type']}, {data['carrier']})")
else:
print(f"REJECT: {data['reason']}")
Python with retries (production-ready)
import time
from requests import Session, HTTPError
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = Session()
session.headers.update({"Authorization": f"Bearer {API_KEY}"})
session.mount("https://", HTTPAdapter(max_retries=Retry(
total=3, backoff_factor=1, status_forcelist=[500, 502, 503, 504],
)))
def validate(phone, country=None):
body = {"phone": phone}
if country: body["country"] = country
r = session.post("https://phonevalidationapi.com/api/v1/validate", json=body, timeout=10)
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", 5)))
return validate(phone, country)
r.raise_for_status()
return r.json()
Node.js (fetch / native)
const API_KEY = process.env.PVS_API_KEY;
async function validate(phone, country) {
const body = { phone };
if (country) body.country = country;
const res = await fetch('https://phonevalidationapi.com/api/v1/validate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (res.status === 429) {
const retry = parseInt(res.headers.get('Retry-After') || '5', 10);
await new Promise(r => setTimeout(r, retry * 1000));
return validate(phone, country);
}
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${await res.text()}`);
}
return res.json();
}
const data = await validate('+33612345678');
console.log(data.valid, data.confidence, data.line_type, data.carrier);
Ruby
require 'net/http'
require 'json'
API_KEY = ENV.fetch('PVS_API_KEY')
uri = URI('https://phonevalidationapi.com/api/v1/validate')
req = Net::HTTP::Post.new(uri, {
'Authorization' => "Bearer #{API_KEY}",
'Content-Type' => 'application/json',
})
req.body = { phone: '+33612345678' }.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
if res.code == '200' && data['valid'] && data['confidence'] != 'low'
puts "OK: #{data['formatted']['e164']} (#{data['line_type']}, #{data['carrier']})"
else
puts "REJECT: #{data['reason']}"
end
Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
type ValidateResp struct {
Valid bool `json:"valid"`
Confidence string `json:"confidence"`
Reason string `json:"reason"`
LineType string `json:"line_type"`
Carrier string `json:"carrier"`
Formatted struct {
E164 string `json:"e164"`
} `json:"formatted"`
}
func validate(phone string) (*ValidateResp, error) {
body, _ := json.Marshal(map[string]string{"phone": phone})
req, _ := http.NewRequest("POST", "https://phonevalidationapi.com/api/v1/validate", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PVS_API_KEY"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second}
res, err := client.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
raw, _ := io.ReadAll(res.Body)
if res.StatusCode != 200 {
return nil, fmt.Errorf("HTTP %d: %s", res.StatusCode, raw)
}
var v ValidateResp
json.Unmarshal(raw, &v)
return &v, nil
}
func main() {
v, err := validate("+33612345678")
if err != nil { panic(err) }
fmt.Printf("%+v\n", v)
}
Async batch (POST)
Submit ≤10,000 numbers in one shot:
import requests, os
r = requests.post(
"https://phonevalidationapi.com/api/v1/validate-async",
headers={"Authorization": f"Bearer {os.environ['PVS_API_KEY']}"},
json={
"phones": ["+33612345678", "+14155552671", "+447700900100"],
"country": "FR", # optional fallback
"webhook_url": "https://your-app.com/wh/batch", # optional
"webhook_secret": "shared-secret-for-hmac", # optional
},
)
print(r.json())
# → { "batch_id": "bat_...", "status": "queued", "status_url": "...", ... }
Poll the status:
batch_id = r.json()["batch_id"]
while True:
s = requests.get(
f"https://phonevalidationapi.com/api/v1/batch/{batch_id}",
headers={"Authorization": f"Bearer {os.environ['PVS_API_KEY']}"},
).json()
if s["status"] in ("completed", "failed"):
break
time.sleep(2)
print(f"{s['processed']}/{s['total']} done")
for r in s["results"]:
print(r["formatted"]["e164"], r["valid"], r["confidence"])
Webhook signature verification
When you supply webhook_secret, we sign the callback body with HMAC-SHA256:
Python
import hmac, hashlib
def verify_phonevalidation_signature(raw_body: bytes, sig_header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig_header)
Node.js
import crypto from 'node:crypto';
export function verify(rawBody, sigHeader, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(sigHeader), Buffer.from(expected));
}
The signature is in the X-PhoneValidation-Signature header. The full body is the raw POST body — don't re-serialize.
More
- Need an SDK? We don't ship one — REST is enough. If you want one, open a GitHub issue / contact us.
- Postman collection:
/api/v1/postman.json - OpenAPI 3.0 spec:
/api/v1/openapi.json