PhoneCheck

API docs

A JSON API that validates and formats phone numbers offline, using Google's libphonenumber numbering-plan metadata (libphonenumber-js 1.13.14, "max" set, 245 regions). Base URL: https://phonecheck

Numbering-plan check only. No carrier, HLR or live-line lookup. "Valid" means the number matches a range allocated in the metadata. PhoneCheck does not know whether a number is connected, reachable, active, or who holds it, and it does not identify the carrier. Line type reflects the original allocation; ported numbers may differ. Don't use it as your only fraud, KYC or deliverability control.

Endpoints

Method and pathWhat it doesKey
POST /v1/parseValidate and format one number. Body {"number": "...", "defaultCountry": "US"} (defaultCountry optional). Recommended.optional
GET /v1/parse/{number}Same, with the number in the URL (URL-encode + as %2B; optional ?defaultCountry=GB). Staging / quick tests only — disabled in production (PUBLIC_LAUNCH=true → 405). Prefer POST: URLs end up in logs and history.optional (staging)
POST /v1/bulkUp to 500 numbers in one request, for normalising listsPro key
GET /v1/example/{country}Example (mobile) number for a region in every formatoptional
GET /v1/countriesSupported region codes, names and calling codesoptional
GET /v1/metaVersion, build, metadata version, limitsoptional

POST/v1/parse

Send the number as typed: spaces, dashes, dots, brackets, a leading 00/+ and extensions (ext. 89, x89) are understood. Numbers without a +country code need defaultCountry (ISO 3166-1 alpha-2, e.g. GB). Max 64 characters; body max 16 KB.

curl -X POST https://phonecheck/v1/parse \
  -H "Content-Type: application/json" \
  -d '{"number": "020 7946 0018", "defaultCountry": "GB"}'
{
  "input": "020 7946 0018",
  "default_country": "GB",
  "valid": true,
  "possible": true,
  "e164": "+442079460018",
  "international": "+44 20 7946 0018",
  "national": "020 7946 0018",
  "rfc3966": "tel:+442079460018",
  "country": "GB",
  "country_name": "United Kingdom",
  "calling_code": "44",
  "national_number": "2079460018",
  "extension": null,
  "line_type": "fixed_line",
  "example": {
    "country": "GB",
    "country_name": "United Kingdom",
    "calling_code": "44",
    "line_type": "mobile",
    "e164": "+447400123456",
    "international": "+44 7400 123456",
    "national": "07400 123456",
    "rfc3966": "tel:+447400123456"
  }
}

An invalid number still returns 200, with valid: false, a reason code and a message:

{
  "input": "+1 555 555 5555",
  "default_country": null,
  "valid": false,
  "possible": true,
  "reason": "not_valid_number",
  "message": "The length is possible, but the digits do not match any number range allocated for this country in the metadata.",
  "e164": "+15555555555",
  "international": "+1 555 555 5555",
  "national": "(555) 555-5555",
  "rfc3966": "tel:+15555555555",
  "country": null,
  "country_name": null,
  "calling_code": "1",
  "national_number": "5555555555",
  "extension": null,
  "line_type": null,
  "possible_countries": [
    "US",
    "AG",
    "AI",
    "AS",
    "BB",
    "BM",
    "BS",
    "CA",
    "DM",
    "DO",
    "GD",
    "GU",
    "JM",
    "KN",
    "KY",
    "LC",
    "MP",
    "MS",
    "PR",
    "SX",
    "TC",
    "TT",
    "VC",
    "VG",
    "VI"
  ]
}

Formats (e164, international, national, rfc3966) are filled in when the number is at least possible (right length); otherwise they are null. line_type is set only for valid numbers, and only when the metadata defines it.

Response fields

FieldMeaning
validMatches an allocated number range for the region (length and leading digits)
possibleHas a possible length for the region (a weaker check than valid)
reason, messageOnly when not valid: not_a_number, invalid_country, too_short, too_long, invalid_length, not_valid_number, input_too_long
e164E.164 form, e.g. +442079460018 (no extension)
international, nationalHuman-readable formats for the region, extension included
rfc3966tel: URI, e.g. tel:+49301234567;ext=89
country, country_nameISO region code and English name. null for non-geographic numbers (non_geographic: true, e.g. +800) and for invalid numbers whose calling code is shared (then possible_countries lists the candidates)
calling_code, national_number, extensionParts of the number
line_typemobile, fixed_line, fixed_line_or_mobile, toll_free, premium_rate, shared_cost, voip, personal_number, pager, uan, voicemail, or null when the metadata can't tell
exampleA mobile example number for the region (single-number endpoints only), for format hints

POST/v1/bulk (Pro key)

Normalise a list: up to 500 numbers per request, body max 256 KB. Items are strings, or objects {"number": "...", "defaultCountry": "FR"} to override the request-level defaultCountry. Each number counts as one call toward the monthly quota; the whole request is refused (nothing charged) if it doesn't fit in what's left. Results come back in input order with the same fields as /v1/parse (minus example), plus index.

curl -X POST https://phonecheck/v1/bulk \
  -H "Authorization: Bearer pc_live_..." -H "Content-Type: application/json" \
  -d '{"defaultCountry": "US", "numbers": ["(213) 373-4253", "+44 20 7946 0018", {"number": "01 23 45 67 89", "defaultCountry": "FR"}, "12"]}'
{
  "count": 4,
  "valid_count": 3,
  "invalid_count": 1,
  "unique_valid_e164": 3,
  "default_country": "US",
  "results": [
    {
      "index": 0,
      "input": "(213) 373-4253",
      "valid": true,
      "e164": "+12133734253",
      "country": "US",
      "line_type": "fixed_line_or_mobile",
      "...": "(same fields as /v1/parse)"
    },
    {
      "index": 1,
      "input": "+44 20 7946 0018",
      "valid": true,
      "e164": "+442079460018",
      "country": "GB",
      "line_type": "fixed_line",
      "...": "(same fields as /v1/parse)"
    },
    {
      "index": 2,
      "input": "01 23 45 67 89",
      "valid": true,
      "e164": "+33123456789",
      "country": "FR",
      "line_type": "fixed_line",
      "...": "(same fields as /v1/parse)"
    },
    {
      "index": 3,
      "input": "12",
      "valid": false,
      "e164": null,
      "country": "US",
      "line_type": null,
      "reason": "too_short",
      "...": "(same fields as /v1/parse)"
    }
  ]
}

GET/v1/example/{country}

{
  "country": "DE",
  "country_name": "Germany",
  "calling_code": "49",
  "line_type": "mobile",
  "e164": "+4915123456789",
  "international": "+49 1512 3456789",
  "national": "01512 3456789",
  "rfc3966": "tel:+4915123456789"
}

Example numbers come from the libphonenumber metadata. They are for testing formats; don't dial them.

API keys and limits

No key is needed for light use. Send a key as Authorization: Bearer pc_live_... or X-Api-Key: pc_live_.... Keys are issued by the operator in this version (no self-serve sign-up yet).

TierRateMonthly callsBulk
Anonymous (no key)20 requests/minute per IPshared daily capacityno
Free key300 requests/minute1,000no
Pro key300 requests/minute50,000 (a bulk request counts one per number)up to 500 numbers/request

Keyed responses carry X-Quota-Limit and X-Quota-Remaining. Quotas reset at 00:00 UTC on the 1st. A global daily soft cap protects the service; when it is reached, requests get 429 daily_capacity_reached until 00:00 UTC.

Errors

Errors are JSON: {"error": {"code": "...", "message": "..."}}.

StatusCodes
400invalid_json, invalid_body, missing_number, input_too_long, invalid_default_country, number_in_url, invalid_bulk, empty_bulk, bulk_too_large, bad_encoding
401invalid_api_key, api_key_required
403paid_plan_required (bulk with a free key)
404not_found, unknown_country
405method_not_allowed
413payload_too_large (single 16 KB, bulk 256 KB)
429rate_limited, monthly_quota_exceeded, daily_capacity_reached (with Retry-After)
503storage_unavailable (key lookup briefly unavailable)

Limitations

Privacy

Credits

Numbering-plan metadata: Google libphonenumber (Apache-2.0), packaged by libphonenumber-js (MIT).