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
Endpoints
| Method and path | What it does | Key |
|---|---|---|
POST /v1/parse | Validate 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/bulk | Up to 500 numbers in one request, for normalising lists | Pro key |
GET /v1/example/{country} | Example (mobile) number for a region in every format | optional |
GET /v1/countries | Supported region codes, names and calling codes | optional |
GET /v1/meta | Version, build, metadata version, limits | optional |
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
| Field | Meaning |
|---|---|
valid | Matches an allocated number range for the region (length and leading digits) |
possible | Has a possible length for the region (a weaker check than valid) |
reason, message | Only when not valid: not_a_number, invalid_country, too_short, too_long, invalid_length, not_valid_number, input_too_long |
e164 | E.164 form, e.g. +442079460018 (no extension) |
international, national | Human-readable formats for the region, extension included |
rfc3966 | tel: URI, e.g. tel:+49301234567;ext=89 |
country, country_name | ISO 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, extension | Parts of the number |
line_type | mobile, 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 |
example | A 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).
| Tier | Rate | Monthly calls | Bulk |
|---|---|---|---|
| Anonymous (no key) | 20 requests/minute per IP | shared daily capacity | no |
| Free key | 300 requests/minute | 1,000 | no |
| Pro key | 300 requests/minute | 50,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": "..."}}.
| Status | Codes |
|---|---|
| 400 | invalid_json, invalid_body, missing_number, input_too_long, invalid_default_country, number_in_url, invalid_bulk, empty_bulk, bulk_too_large, bad_encoding |
| 401 | invalid_api_key, api_key_required |
| 403 | paid_plan_required (bulk with a free key) |
| 404 | not_found, unknown_country |
| 405 | method_not_allowed |
| 413 | payload_too_large (single 16 KB, bulk 256 KB) |
| 429 | rate_limited, monthly_quota_exceeded, daily_capacity_reached (with Retry-After) |
| 503 | storage_unavailable (key lookup briefly unavailable) |
Limitations
- No carrier, HLR, porting or reachability data. PhoneCheck never contacts a network. It can't tell you whether a number is in service, which carrier serves it now, or whether it can receive SMS.
- Line type comes from the numbering plan. Mobile numbers ported to fixed or VoIP services (and vice versa) keep their original type. US and Canada numbers are
fixed_line_or_mobilebecause the plan doesn't separate them. - Metadata is a snapshot (libphonenumber-js 1.13.14). New number ranges may show as
not_valid_numberuntil the metadata is updated. - Short codes, emergency numbers and SMS short numbers are not validated.
- Bulk max 500 numbers per request (Pro). Chosen so a cold isolate stays under the Worker
cpu_ms = 100cap (a cold 1,000-number bulk once measured ~85 ms). Split larger lists. Atlas can raisecpu_msif a larger bulk is needed later. GET /v1/parse/{number}is for staging / quick tests only. Production returns 405 and points you toPOST /v1/parseso numbers never land in URL paths or proxy logs.
Privacy
- Numbers you send are processed in memory to build the response and then discarded. PhoneCheck does not store them, log them, or send them anywhere (no third-party lookups, no analytics).
- Workers observability/logging is turned off for this service. The only data kept is counters: calls per API key per month, and total calls per day.
- Use
POSTso numbers stay out of URLs. The web checker on the home page always uses POST. - API keys are stored as SHA-256 hashes; no email or personal data is needed for a key.
- Like any HTTPS service on Cloudflare, Cloudflare processes connection metadata (such as IP address) to deliver and protect the service; per-IP rate limiting uses it transiently.
Credits
Numbering-plan metadata: Google libphonenumber (Apache-2.0), packaged by libphonenumber-js (MIT).