One endpoint: send a phone number, get back whether it's real, which country and area it's from, what line type it is, and whether its prefix is currently associated with reported scams. JSON in, JSON out.
Every request needs an X-Api-Key header. Get a free key at /api/login - no credit card, no forms beyond an email address for the magic link.
X-Api-Key: dc_your_key
Requests without a valid key get 401 with {"error": "invalid_api_key"}.
GET /v1/phone/{number}
{number} is any phone number in international format. The leading + works as-is in the URL path - no encoding needed (%2B also accepted if your HTTP client encodes it). Numbers sent without the + (e.g. 13214991734) are parsed as international automatically. Numbers without enough digits, or that don't parse to a real dialing plan, return a valid: false result rather than an error - with a hint field saying what would resolve them.
?country=A number written the way people write it locally - 8663982896, 866-398-2896, (866) 398-2896 - carries no country code, so it cannot be resolved on its own. Pass the country and it will be read against that numbering plan:
$ curl "https://dialcode.app/v1/phone/8663982896?country=US" \
-H "X-Api-Key: dc_your_key"
{ "valid": true, "e164": "+18663982896", "country": { "code": "US", ... } }
country takes an ISO 3166-1 alpha-2 code (case-insensitive) and applies to that one lookup. An unknown code returns 400 invalid_country and does not spend quota. The same option exists for CSV uploads in the dashboard, where it applies to every row of the file.
If all your numbers come from one market, set a default country on your key in the dashboard instead: every lookup and upload that names no country is read against it. An explicit ?country= still wins.
When a number still comes back valid: false, reason says which kind of false it is: needs_country means the digits are a real national number somewhere and candidates lists the countries that accept them; not_a_number means no numbering plan does.
$ curl https://dialcode.app/v1/phone/+639171234567 \
-H "X-Api-Key: dc_your_key"
{
"valid": true,
"e164": "+639171234567",
"country": { "code": "PH", "dial_code": "+63", "name": "Philippines", "timezone": "Asia/Manila" },
"formats": { "national": "0917 123 4567", "international": "+63 917 123 4567" },
"carrier": "Globe",
"line_type": "mobile",
"area": { "code": "917", "name": "Manila" },
"risk": {
"scam_risk": "high",
"scam_types": ["wangiri", "romance"],
"advice_url": "https://dialcode.app/scam/63-scam",
"complaints": null,
"signals": []
}
}
| Field | Type | Description |
|---|---|---|
valid | boolean | Whether the number parses as a real, dialable number. |
e164 | string | null | Normalized E.164 form. Null when valid is false. |
reason | string | Present only when valid is false: needs_country (a real national number, country missing) or not_a_number (no numbering plan accepts it). |
candidates | array of strings | Present only when valid is false: ISO 3166-1 alpha-2 codes the input resolves under, US first then alphabetical. Empty for not_a_number and whenever country was given. |
hint | string | Present only when valid is false: what would make the number resolve, in words. Names the countries to pass when the input looks like a national-format number. |
country.code | string | null | ISO 3166-1 alpha-2 country code (e.g. PH). |
country.dial_code | string | null | International dialing code (e.g. +63). |
country.name | string | null | Country name. |
country.timezone | string | null | IANA timezone of the country's capital (e.g. Asia/Manila). For countries spanning multiple zones this is the capital's zone, not the number's exact zone. |
formats.national | string | null | Number formatted for domestic dialing (e.g. 0917 123 4567). Null when valid is false. |
formats.international | string | null | Human-readable international format with spacing (e.g. +63 917 123 4567). Use e164 for machine comparison. |
carrier | string | null | Carrier the number range was originally allocated to (e.g. Globe, O2). This is the range holder, not necessarily the current operator - numbers moved via porting keep their original range label. Mostly available for mobile numbers; always null for US/Canada (no public carrier data for the NANP) and most landlines. |
line_type | string | null | Line type as detected by number parsing (e.g. mobile, fixed_line). |
area.code | string | null | Local area code, when the country has area-code data and one matches. |
area.name | string | null | City / region for the matched area code. |
risk.scam_risk | string | low, medium, or high - the country's overall scam risk rating. unknown when the number does not resolve to a country but a risk prefix matched (e.g. satellite ranges). |
risk.scam_types | array of strings | Scam patterns reported for this country (e.g. wangiri, romance). Empty array if none documented. |
risk.advice_url | string | null | Link to the full scam profile page for this country, when one exists. |
risk.complaints | object | null | FTC Do Not Call complaint history for this exact number. Present only for US numbers with at least one reported complaint; null for non-US numbers or numbers with no reports. Fields: total (int), robocalls (int, subset of total), first_reported_on / last_reported_on (date), categories (array of {code, count}, sorted by count - what consumers reported the calls were about; codes: imposter, debt_reduction, medical, dropped_call, warranties, tech_support, lottery, timeshares, energy, home_improvement, home_security, work_from_home, charity, unspecified, other; category tracking starts 2026-07-19, so counts can lag total for numbers first reported earlier), geography (object | null - victim spread across US states: states (int, distinct states that reported this number - a broad spread means a nationwide robocall campaign, a single state a targeted local one) and top (up to 5 {state, count} sorted by report count); null until state tracking has data for the number, tracked since 2026-07-19), source (string). |
risk.fcc_complaints | object | null | FCC consumer complaint history (unwanted-calls dataset) for this exact number - an independent second US government source next to the FTC registry. A number present in both is strongly corroborated. Present only for US numbers with at least one FCC complaint. Fields: total (int), first_reported_on / last_reported_on (date), source (string). Tracked since 2026-07-19; the fcc_reported signal fires at 3+ complaints. |
risk.signals | array of objects | Risk signals detected for this specific number: {code, note} pairs from line-type, known scam prefix ranges, and FTC complaint history. Empty array if none detected. See signal codes below. |
Complaint data comes from the FTC's public Do Not Call registry CSVs, ingested daily (weekday publishing schedule - the FTC does not publish weekend files). US numbers only: the Do Not Call registry is a US program.
risk.signals entries come from two layers: line type (detected per-number) and known prefix ranges (detected from the dial code). A number can carry signals from both layers at once.
| Code | Layer | Meaning |
|---|---|---|
voip_line | line type | VoIP number: cheap to obtain anonymously, common in fraud and SMS-verification bypass. |
premium_rate_line | line type | Premium-rate number: calling it incurs elevated charges. |
shared_cost_line | line type | Shared-cost number: partial elevated charges apply. |
satellite_range | prefix | Satellite range (+870, +881): very high callback charges, classic wangiri bait. |
international_network_range | prefix | International network range (+882, +883): ITU non-geographic codes outside normal country dialing plans. |
premium_redirect_range | prefix | UK personal numbering range (+4470): missed-call redirect and premium forwarding. |
premium_rate_range | prefix | US 900 premium-rate range (+1900): calls billed at elevated per-minute rates. |
ftc_reported | complaints | US number reported to the FTC Do Not Call registry 3 or more times. |
| Status | error | Meaning |
|---|---|---|
| 401 | invalid_api_key | Missing or invalid X-Api-Key header. |
| 400 | unparseable_number | Input is too short (under 4 digits) to attempt a lookup. |
| 400 | invalid_country | country is not an ISO 3166-1 alpha-2 code we know. No quota spent. |
| 429 | quota_exceeded | Monthly quota used up for this key. |
| 429 | rate_limited | Per-minute rate limit exceeded, independent of the monthly quota. |
All errors return JSON with a machine code and a human message: {"error": "quota_exceeded", "message": "Monthly quota exhausted. It resets on the 1st (UTC) - see Retry-After."}. Both 429 variants ship a Retry-After header (seconds).
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (unix epoch of the next monthly reset).Quota state for the calling key. Free - consumes no quota, logs nothing.
$ curl https://dialcode.app/v1/account/usage -H "X-Api-Key: dc_your_key"
{
"key_prefix": "dc_1a2b3c4d",
"monthly_quota": 1000,
"used": 250,
"remaining": 750,
"resets_on": "2026-08-01"
}
curl https://dialcode.app/v1/phone/+639171234567 \ -H "X-Api-Key: dc_your_key"
const res = await fetch("https://dialcode.app/v1/phone/+639171234567", {
headers: { "X-Api-Key": "dc_your_key" }
});
const data = await res.json();
console.log(data.risk.scam_risk);
require "net/http"
require "json"
uri = URI("https://dialcode.app/v1/phone/+639171234567")
req = Net::HTTP::Get.new(uri)
req["X-Api-Key"] = "dc_your_key"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
puts data["risk"]["scam_risk"]
import requests
res = requests.get(
"https://dialcode.app/v1/phone/+639171234567",
headers={"X-Api-Key": "dc_your_key"},
)
print(res.json()["risk"]["scam_risk"])
$ch = curl_init("https://dialcode.app/v1/phone/+639171234567");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: dc_your_key"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = json_decode(curl_exec($ch), true);
echo $data["risk"]["scam_risk"];
Validate a whole file at once: upload a CSV (one number per line or first column, 10,000 rows / 1 MB max) in the dashboard. Each parsed row spends one quota request; the result CSV (validity, country, carrier, scam risk, FTC/FCC complaint totals, signals) is ready for download within a minute and kept for 7 days.