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