Every central office code in the North American Numbering Plan, matched to the ZIP Codes it serves and the carrier that holds it. Last updated June23, 2026 (2 months ago).
For use inside your own organization — number validation, call routing, CRM enrichment, analysis and internal systems.
Read the termsIf the data ships inside your product — a validator, a store locator, a mobile app — you need this one instead. Unlimited and royalty-free.
Read the redistribution termsBoth editions carry the same 562,060 rows and the same coverage. Standard tells you a number exists and roughly where it is. Deluxe tells you who runs it, what kind of line it is, and where the people it serves actually live.
| What you can do with it | Deluxe$449.95/yr | Standard$249.95/yr |
|---|---|---|
| Validating a phone number | ||
| Confirm an NPA-NXX exists at allNPA · NXX · NPANXX — every assigned central office code in the plan | ✓ | ✓ |
| Place the number in a state or provinceState — two-letter code, U.S. states, Canadian provinces and NANP territories | ✓ | ✓ |
| Match a number to a ZIP CodeZipCode · ZipCodeCount · ZipCodeFreq — an exchange usually reaches several ZIPs, so ZipCodeFreq tells you the main one | ✓ | ✓ |
| Know the regulatory calling regionLATA — Local Access and Transport Area | ✓ | ✓ |
| Reject numbers in retired exchangesStatus — 96,650 of the 562,060 rows are Inactive and ship on purpose, because rejecting a number needs them | ✓ | Standard ends here |
| Knowing who runs the number | ||
| Identify the carrier holding the codeCompany · OCN — the Operating Company Number and its name | 79%of records | |
| Tell a landline from a wireless numberNXXUseType — L or W. The single most-asked-for field on this product, and what TCPA screening runs on. | 83%of records | |
| Route by rate centerRateCenter · CoverageAreaName | ✓ | |
| Spot a newly opened exchangeNXXIntroVersion — the release in which the NXX first appeared | ✓ | |
| Know an area code is an overlayOverlay — L marks a recent overlay on an existing geographic area | ✓ | |
| Putting it on a map | ||
| Place the central officeLatitude · Longitude — where the switch is | 83%resolved | |
| Place the people the exchange servesWeightedLat · WeightedLon — the centroid of the ZIP Code the exchange most often reaches. For territories, lead routing and customer mapping this is the pair you want, not the switch. | 78%resolved | |
| Name the city and countyCity · County · CoverageAreaName | ✓ | |
| Join to federal geographyFIPS · MSA_CBSA · MSA_CBSA_CODE — county FIPS and the Census statistical area | ✓ | |
| Calling at the right time | ||
| Know the time zoneTimeZone — hours behind GMT, 4 Atlantic through 11 and beyond for the Pacific territories | ✓ | |
| Know whether it observes DSTObservesDST — Y or N. Arizona outside the Navajo Nation, Hawaii, Puerto Rico and parts of Indiana do not. | ✓ | |
| Data fields | 31 | 9a strict subset — same names, same meanings |
| $449.95 /year Purchase Options Download free sample | $249.95 /year Purchase Options Download free sample | |
Why the percentages, and why they are not higher. The numbering plan includes codes that are reserved, unassigned to a carrier, or held by an administrator rather than an operator, and Canadian and territory records are carried for reference rather than maintained to the same standard. Those rows are still in the file — you need them to recognize a code — but they have no carrier to name and no serving office to place. Roughly four rows in five carry the full picture. We would rather show you the figure than a tick that implies five in five.
Two coordinates, two different questions. Latitude / Longitude
place the central office — the switch. WeightedLat /
WeightedLon place the centroid of the ZIP Code the exchange most often serves
— the people. If you are drawing sales territories, routing leads or mapping
customers, the weighted pair is almost always the one you want; the switch can sit in an
industrial park miles from anyone it serves.
Reading the colors: populated, with the real coverage figure ✓ present on every row Hatched = not in that edition.
Both editions cover the whole North American Numbering Plan — all 50 states, DC, Puerto Rico, Guam, the U.S. Virgin Islands, the Northern Mariana Islands, American Samoa and the Canadian provinces and territories — delivered as a single file in CSV, tab-delimited and Microsoft Access formats. Field counts and coverage percentages measured from the June2026 build. See every column on the technical specifications page.
Real records from the production file, not mock data, so you can test your importer against the actual layout before you buy. Each sample carries the data in all three formats and that edition’s specification sheet. No email required.
31 columns
The full layout — carrier, OCN, rate center, landline/wireless, both coordinate pairs, time zone, county, FIPS and CBSA.
CSV · TAB · MDB
Download sample 226 KB9 columns
The code, the state, the LATA and the ZIP Code correlation with its frequency ranking — enough to validate a number and place it.
CSV · TAB · MDB
Download sample 202 KBWant the column list before you download? Every field, with its type, how much of it is populated, and which editions carry it.
See every column View pricing & editionsThe first six digits of a North American phone number, and the level at which telecom geography is actually assigned. There are only 431 area codes — but 562,060 rows in this file, and the difference is the whole product.
212 in (212) 555-1234.555.212555. This is the key almost
every system indexes on.ZipCodeFreq ranks those: which ZIP the exchange mostly serves,
which is second, and by how much.NPANXX is not unique in this file. Filter on frequency
if you want one row per exchange.That last box is the honest one. If you need portability or live status, no downloadable file can give it to you — ours included.
Read the FAQs Look at real rowsNine things customers actually do with this file. Each one names the columns it runs on and the edition that carries them — which is worth reading before you choose, because carrier, line type, coordinates and time zone all live in Deluxe.
Validate phone numbers in web forms, CRM imports or batch pipelines. Confirm an area code and exchange combination exists before you attempt delivery, cutting bounce rates and cleaning up customer records. Deluxe adds Status, so you can also reject numbers in retired exchanges rather than only unknown ones.
Key fields: NPANXX, NPA, NXX, State
Works on StandardRoute inbound calls based on area code, time zone, or rate center. Implement least-cost routing using carrier and LATA data. Perfect for contact centers, VoIP platforms, and toll-free number management.
Key fields: NPA, NXX, RateCenter, TimeZone, LATA, Carrier, State
Needs DeluxeFlag suspicious numbers from inactive NPA-NXX blocks or newly assigned exchanges. Cross-reference carrier data to detect caller ID spoofing. Identify high-risk area codes and verify caller authenticity in real-time authentication systems.
Key fields: Status, NXXIntroVersion, Company, OCN, State
Needs DeluxeAppend geographic location, carrier information, and service type to existing customer phone numbers. Enhance marketing segmentation with wireless/landline classification. Fill gaps in incomplete customer records with ZIP code correlations.
Key fields: City, County, State, ZipCode, NXXUseType, Company
Needs DeluxeSchedule outbound calls during appropriate business hours based on phone number time zones. Respect DST observance for Arizona and Hawaii. Optimize contact center staffing by predicting call volume across time zones.
Key fields: TimeZone, ObservesDST, State, RateCenter
Needs DeluxeAnalyze call patterns by region, carrier type, or service area. Track number pool assignments over time using historical status data. Generate market intelligence reports on telecom infrastructure and carrier distribution.
Key fields: OCN, Company, MSA_CBSA, County, NXXUseType, Status
Needs DeluxeRoute emergency calls to appropriate dispatch centers using rate center and county data. Determine jurisdiction boundaries for 911 call handling. Support VoIP E911 compliance requirements with accurate geographic attribution.
Key fields: RateCenter, County, State, FIPS, Latitude, Longitude
Needs DeluxeIdentify wireless numbers before calling to comply with TCPA regulations. Maintain proper consent records for mobile marketing campaigns. Reduce legal risk by classifying phone types before outbound marketing activities.
Key fields: NXXUseType, State, Status
Needs DeluxeTarget marketing campaigns by geographic region using area code intelligence. Display local phone numbers to visitors based on their location. Optimize service coverage by mapping customer density to rate centers and MSAs.
Key fields: State, City, County, MSA_CBSA, ZipCode, Latitude, Longitude
Needs DeluxeSeven of these nine need Deluxe. Carrier, line type, coordinates and time zone are the columns doing the work.
See what each edition carries View pricingThe same lookup in six languages: read the file, index it correctly, and resolve a phone number to a location, a carrier and a line type. Written against the real column names and the real quirks — not pseudocode.
utf-8-sig,
or strip EF BB BF yourself — otherwise your first column is
named \uFEFFState and every lookup on State misses.NPANXX is not unique. An exchange appears once per ZIP Code
it serves, so keying a map on it keeps only the last one. Rank on
ZipCodeFreq and keep the primary, or use a composite key.ZipCodeFreq has sentinel values. It is a percentage, except
that -1 means “most frequent, share unknown”,
-2 means “second most frequent” and 0
means unknown. Sorting on the raw number puts the best row last.import csv
# encoding="utf-8-sig" strips the BOM. With plain "utf-8" the first
# column is named "\uFEFFNPA" and every lookup on it silently fails.
PATH = "DeluxeAreaCodeDatabase.csv"
def rank(freq):
"""ZipCodeFreq is a percentage EXCEPT for three sentinels:
-1 = most frequent (share unknown), -2 = second, 0 = unknown.
Map them onto one scale so a plain max() picks the right row."""
f = int(freq or 0)
if f == -1:
return 101.0
if f == -2:
return 100.0
if f == 0:
return -1.0
return float(f)
# NPANXX is NOT unique - an exchange appears once per ZIP Code it
# serves. Keep the PRIMARY ZIP for each exchange rather than whichever
# row happened to come last.
exchanges = {}
with open(PATH, encoding="utf-8-sig", newline="") as fh:
for row in csv.DictReader(fh):
key = row["NPANXX"]
best = exchanges.get(key)
if best is None or rank(row["ZipCodeFreq"]) > rank(best["ZipCodeFreq"]):
exchanges[key] = row
def lookup(phone):
digits = "".join(c for c in phone if c.isdigit())
if len(digits) == 11 and digits[0] == "1": # leading country code
digits = digits[1:]
if len(digits) < 6:
return {"valid": False, "reason": "too short"}
row = exchanges.get(digits[:6])
if row is None:
return {"valid": False, "npanxx": digits[:6],
"reason": "no such exchange"}
# A coordinate of 0,0 is a null wearing a number's clothes.
lat, lon = float(row["WeightedLat"]), float(row["WeightedLon"])
return {
"valid": True,
"npanxx": row["NPANXX"],
"active": row["Status"] == "Active",
"state": row["State"],
"city": row["City"] or None,
"zip": row["ZipCode"] or None,
"carrier": row["Company"] or None,
"line": {"L": "landline", "W": "wireless"}.get(row["NXXUseType"]),
"tz": row["TimeZone"] or None,
"dst": row["ObservesDST"] == "Y",
"point": (lat, lon) if (lat or lon) else None,
}
r = lookup("(212) 555-1234")
print(r["state"], r["city"], r["carrier"], r["line"], r["active"])
Memory: the full Deluxe file is 562,060 rows; indexed this way you keep one row per exchange, which is roughly 200,000 entries and about 300–400 MB as Python dicts. If that is too much, keep only the seven fields you need per row, or load it into SQLite and query it.
<?php
$path = 'DeluxeAreaCodeDatabase.csv';
// ZipCodeFreq sentinels: -1 most frequent, -2 second, 0 unknown.
function zipRank($freq) {
$f = (int)$freq;
if ($f === -1) return 101.0;
if ($f === -2) return 100.0;
if ($f === 0) return -1.0;
return (float)$f;
}
$exchanges = [];
$fh = fopen($path, 'r');
// fgetcsv does not strip the BOM, so the first header would come back
// as "\xEF\xBB\xBFNPA". Remove it from the header row by hand.
$headers = fgetcsv($fh);
$headers[0] = preg_replace('/^\xEF\xBB\xBF/', '', $headers[0]);
while (($cols = fgetcsv($fh)) !== false) {
if (count($cols) !== count($headers)) continue;
$row = array_combine($headers, $cols);
$key = $row['NPANXX'];
// keep the PRIMARY ZIP - NPANXX repeats once per ZIP Code served
if (!isset($exchanges[$key])
|| zipRank($row['ZipCodeFreq']) > zipRank($exchanges[$key]['ZipCodeFreq'])) {
$exchanges[$key] = $row;
}
}
fclose($fh);
function lookupPhone($phone, $exchanges) {
$digits = preg_replace('/\D/', '', $phone);
if (strlen($digits) === 11 && $digits[0] === '1') {
$digits = substr($digits, 1);
}
if (strlen($digits) < 6) {
return ['valid' => false, 'reason' => 'too short'];
}
$npanxx = substr($digits, 0, 6);
if (!isset($exchanges[$npanxx])) {
return ['valid' => false, 'npanxx' => $npanxx,
'reason' => 'no such exchange'];
}
$r = $exchanges[$npanxx];
$lat = (float)$r['WeightedLat'];
$lon = (float)$r['WeightedLon'];
return [
'valid' => true,
'npanxx' => $r['NPANXX'],
'active' => $r['Status'] === 'Active',
'state' => $r['State'],
'city' => $r['City'] ?: null,
'zip' => $r['ZipCode'] ?: null,
'carrier' => $r['Company'] ?: null,
'line' => ['L' => 'landline', 'W' => 'wireless'][$r['NXXUseType']] ?? null,
'point' => ($lat || $lon) ? [$lat, $lon] : null,
];
}
$r = lookupPhone('(212) 555-1234', $exchanges);
echo $r['state'], ' ', $r['city'], ' ', $r['carrier'], PHP_EOL;
Do not do this per request. Parsing the CSV on every page load will dominate your response time. Build the index once into APCu, Redis or a real table, and read from that.
const fs = require('fs');
const readline = require('readline');
const PATH = 'DeluxeAreaCodeDatabase.csv';
// ZipCodeFreq sentinels: -1 most frequent, -2 second, 0 unknown.
const rank = (f) => {
const v = parseInt(f, 10) || 0;
return v === -1 ? 101 : v === -2 ? 100 : v === 0 ? -1 : v;
};
// Minimal CSV split. The Company and MSA_CBSA columns contain commas
// inside quotes, so a plain .split(',') is not enough.
function parseLine(line) {
const out = [];
let cur = '', quoted = false;
for (let i = 0; i < line.length; i++) {
const c = line[i];
if (quoted) {
if (c === '"' && line[i + 1] === '"') { cur += '"'; i++; }
else if (c === '"') quoted = false;
else cur += c;
} else if (c === '"') quoted = true;
else if (c === ',') { out.push(cur); cur = ''; }
else cur += c;
}
out.push(cur);
return out;
}
async function loadExchanges() {
const exchanges = new Map();
let headers = null;
const rl = readline.createInterface({
input: fs.createReadStream(PATH, 'utf8'),
crlfDelay: Infinity,
});
for await (let line of rl) {
if (!line) continue;
if (!headers) {
// strip the UTF-8 BOM from the very first character
if (line.charCodeAt(0) === 0xFEFF) line = line.slice(1);
headers = parseLine(line);
continue;
}
const cols = parseLine(line);
if (cols.length !== headers.length) continue;
const row = {};
headers.forEach((h, i) => { row[h] = cols[i]; });
// NPANXX repeats once per ZIP Code served - keep the primary
const prev = exchanges.get(row.NPANXX);
if (!prev || rank(row.ZipCodeFreq) > rank(prev.ZipCodeFreq)) {
exchanges.set(row.NPANXX, row);
}
}
return exchanges;
}
function lookup(phone, exchanges) {
let d = String(phone).replace(/\D/g, '');
if (d.length === 11 && d[0] === '1') d = d.slice(1);
if (d.length < 6) return { valid: false, reason: 'too short' };
const r = exchanges.get(d.slice(0, 6));
if (!r) return { valid: false, npanxx: d.slice(0, 6), reason: 'no such exchange' };
const lat = parseFloat(r.WeightedLat), lon = parseFloat(r.WeightedLon);
return {
valid: true,
npanxx: r.NPANXX,
active: r.Status === 'Active',
state: r.State,
city: r.City || null,
carrier: r.Company || null,
line: r.NXXUseType === 'W' ? 'wireless' : r.NXXUseType === 'L' ? 'landline' : null,
point: (lat || lon) ? [lat, lon] : null,
};
}
(async () => {
const exchanges = await loadExchanges();
console.log(lookup('(212) 555-1234', exchanges));
})();
Why stream it: fs.readFileSync on the
Deluxe CSV allocates a single ~110 MB string, which on the default heap is enough to
matter. Reading line by line keeps the peak to the index you are building.
-- ---------------------------------------------------------------
-- NPANXX is NOT unique. One exchange appears once per ZIP Code it
-- serves, so NPANXX cannot be the primary key - the import fails on
-- the second row. The natural key is (NPANXX, ZipCode).
-- ---------------------------------------------------------------
CREATE TABLE npanxx (
NPANXX CHAR(6) NOT NULL,
NPA CHAR(3) NOT NULL,
NXX CHAR(3) NOT NULL,
ZipCode CHAR(5) NULL, -- CHAR, not INT: leading zeros
ZipCodeCount INT NULL,
ZipCodeFreq SMALLINT NULL, -- -1 / -2 / 0 are sentinels
State CHAR(2) NULL,
City VARCHAR(64) NULL,
County VARCHAR(64) NULL,
FIPS CHAR(5) NULL, -- CHAR: leading zeros
LATA VARCHAR(8) NULL,
TimeZone TINYINT NULL,
ObservesDST CHAR(1) NULL, -- 'Y' / 'N', NOT 1/0
NXXUseType CHAR(1) NULL, -- 'L' landline, 'W' wireless
RateCenter VARCHAR(16) NULL,
OCN CHAR(4) NULL, -- CHAR: alphanumeric, can lead 0
Company VARCHAR(96) NULL,
Status VARCHAR(8) NULL,
Latitude DECIMAL(12,8) NULL, -- the switch
Longitude DECIMAL(12,8) NULL,
WeightedLat DECIMAL(12,8) NULL, -- the people
WeightedLon DECIMAL(12,8) NULL,
PRIMARY KEY (NPANXX, ZipCode)
);
CREATE INDEX idx_npanxx ON npanxx (NPANXX);
CREATE INDEX idx_state ON npanxx (State);
CREATE INDEX idx_ocn ON npanxx (OCN);
-- MySQL import. CHARACTER SET utf8mb4 handles the BOM as part of the
-- first field of the header row, which IGNORE 1 ROWS then discards.
LOAD DATA INFILE 'DeluxeAreaCodeDatabase.csv'
INTO TABLE npanxx
CHARACTER SET utf8mb4
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\r\n'
IGNORE 1 ROWS (NPA, NXX, @CountyPop, ZipCodeCount, ZipCodeFreq,
Latitude, Longitude, State, City, County, TimeZone,
ObservesDST, NXXUseType, @NXXIntroVersion, ZipCode,
@NPANew, FIPS, LATA, @Overlay, RateCenter, @SwitchCLLI,
@MSA, @MSACode, OCN, Company, @CoverageAreaName,
NPANXX, @Flags, Status, WeightedLat, WeightedLon);
-- ---------------------------------------------------------------
-- One row per exchange: the PRIMARY ZIP Code, ranked past the
-- sentinel values. This is the view most applications should query.
-- ---------------------------------------------------------------
CREATE VIEW npanxx_primary AS
SELECT n.*
FROM npanxx n
JOIN (
SELECT NPANXX,
MAX(CASE ZipCodeFreq WHEN -1 THEN 101
WHEN -2 THEN 100
WHEN 0 THEN -1
ELSE ZipCodeFreq END) AS best
FROM npanxx GROUP BY NPANXX
) b ON b.NPANXX = n.NPANXX
AND b.best = CASE n.ZipCodeFreq WHEN -1 THEN 101
WHEN -2 THEN 100
WHEN 0 THEN -1
ELSE n.ZipCodeFreq END;
-- Validate one number
SELECT NPANXX, State, City, Company AS Carrier,
CASE NXXUseType WHEN 'W' THEN 'Wireless'
WHEN 'L' THEN 'Landline' END AS LineType,
Status, TimeZone, WeightedLat, WeightedLon
FROM npanxx_primary
WHERE NPANXX = LEFT(REGEXP_REPLACE('(212) 555-1234', '[^0-9]', ''), 6);
-- Enrich a customer table. Join the VIEW, not the table, or every
-- customer multiplies by the number of ZIP Codes their exchange serves.
UPDATE customers c
JOIN npanxx_primary n ON n.NPANXX = LEFT(c.phone_digits, 6)
SET c.phone_state = n.State,
c.phone_city = n.City,
c.phone_carrier = n.Company,
c.phone_type = CASE n.NXXUseType WHEN 'W' THEN 'Wireless'
WHEN 'L' THEN 'Landline' END
WHERE c.phone_digits IS NOT NULL;
The join above is the one that goes wrong. Joining
npanxx directly instead of npanxx_primary multiplies every
customer row by the number of ZIP Codes their exchange reaches — often three or
four, sometimes twenty. It is a silent bug: the update succeeds and the row counts
look plausible.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
public sealed record Exchange(
string Npanxx, string State, string City, string Zip,
string Carrier, string LineType, bool Active,
double? Lat, double? Lon);
public static class NpaNxxIndex
{
// ZipCodeFreq sentinels: -1 most frequent, -2 second, 0 unknown.
static double Rank(string freq) =>
int.TryParse(freq, out var f)
? f switch { -1 => 101, -2 => 100, 0 => -1, _ => f }
: -1;
public static Dictionary<string, Exchange> Load(string path)
{
// new UTF8Encoding(true) plus detectEncodingFromByteOrderMarks
// consumes the BOM; without it the first header is "\uFEFFNPA".
using var rdr = new StreamReader(path, Encoding.UTF8, true);
var header = SplitCsv(rdr.ReadLine()!);
var idx = header
.Select((h, i) => (h, i))
.ToDictionary(t => t.h, t => t.i);
var best = new Dictionary<string, (double rank, Exchange ex)>();
string? line;
while ((line = rdr.ReadLine()) != null)
{
if (line.Length == 0) continue;
var c = SplitCsv(line);
if (c.Length != header.Length) continue;
string Get(string col) => c[idx[col]];
var key = Get("NPANXX");
var rank = Rank(Get("ZipCodeFreq"));
if (best.TryGetValue(key, out var prev) && prev.rank >= rank)
continue;
double.TryParse(Get("WeightedLat"), NumberStyles.Float,
CultureInfo.InvariantCulture, out var lat);
double.TryParse(Get("WeightedLon"), NumberStyles.Float,
CultureInfo.InvariantCulture, out var lon);
best[key] = (rank, new Exchange(
key, Get("State"),
Get("City") is { Length: > 0 } ci ? ci : null!,
Get("ZipCode") is { Length: > 0 } z ? z : null!,
Get("Company") is { Length: > 0 } co ? co : null!,
Get("NXXUseType") switch { "L" => "landline",
"W" => "wireless", _ => null! },
Get("Status") == "Active",
(lat == 0 && lon == 0) ? null : lat,
(lat == 0 && lon == 0) ? null : lon));
}
return best.ToDictionary(kv => kv.Key, kv => kv.Value.ex);
}
public static Exchange? Lookup(
IReadOnlyDictionary<string, Exchange> index, string phone)
{
var d = new string(phone.Where(char.IsDigit).ToArray());
if (d.Length == 11 && d[0] == '1') d = d[1..];
if (d.Length < 6) return null;
return index.TryGetValue(d[..6], out var ex) ? ex : null;
}
static string[] SplitCsv(string line)
{
var outp = new List<string>();
var sb = new StringBuilder();
bool q = false;
for (int i = 0; i < line.Length; i++)
{
var ch = line[i];
if (q)
{
if (ch == '"' && i + 1 < line.Length && line[i + 1] == '"')
{ sb.Append('"'); i++; }
else if (ch == '"') q = false;
else sb.Append(ch);
}
else if (ch == '"') q = true;
else if (ch == ',') { outp.Add(sb.ToString()); sb.Clear(); }
else sb.Append(ch);
}
outp.Add(sb.ToString());
return outp.ToArray();
}
}
Culture matters here. Parse the coordinates with
CultureInfo.InvariantCulture. On a machine with a comma decimal
separator, double.Parse("40.7175") returns 407175.
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
public final class NpaNxxIndex {
public record Exchange(String npanxx, String state, String city,
String zip, String carrier, String lineType,
boolean active, Double lat, Double lon) {}
// ZipCodeFreq sentinels: -1 most frequent, -2 second, 0 unknown.
private static double rank(String freq) {
try {
int f = Integer.parseInt(freq.trim());
return switch (f) { case -1 -> 101; case -2 -> 100;
case 0 -> -1; default -> f; };
} catch (Exception e) { return -1; }
}
public static Map<String, Exchange> load(Path path) throws IOException {
Map<String, Exchange> out = new HashMap<>(220_000);
Map<String, Double> bestRank = new HashMap<>(220_000);
try (BufferedReader r = Files.newBufferedReader(
path, StandardCharsets.UTF_8)) {
String head = r.readLine();
// Files.newBufferedReader does NOT strip the BOM
if (head != null && !head.isEmpty() && head.charAt(0) == '\uFEFF') {
head = head.substring(1);
}
String[] header = split(head);
Map<String, Integer> idx = new HashMap<>();
for (int i = 0; i < header.length; i++) idx.put(header[i], i);
String line;
while ((line = r.readLine()) != null) {
if (line.isEmpty()) continue;
String[] c = split(line);
if (c.length != header.length) continue;
String key = c[idx.get("NPANXX")];
double rk = rank(c[idx.get("ZipCodeFreq")]);
Double prev = bestRank.get(key);
if (prev != null && prev >= rk) continue;
double lat = parse(c[idx.get("WeightedLat")]);
double lon = parse(c[idx.get("WeightedLon")]);
boolean placed = !(lat == 0 && lon == 0);
bestRank.put(key, rk);
out.put(key, new Exchange(
key,
c[idx.get("State")],
blank(c[idx.get("City")]),
blank(c[idx.get("ZipCode")]),
blank(c[idx.get("Company")]),
switch (c[idx.get("NXXUseType")]) {
case "L" -> "landline"; case "W" -> "wireless";
default -> null; },
"Active".equals(c[idx.get("Status")]),
placed ? lat : null,
placed ? lon : null));
}
}
return out;
}
public static Optional<Exchange> lookup(
Map<String, Exchange> index, String phone) {
String d = phone.replaceAll("\\D", "");
if (d.length() == 11 && d.charAt(0) == '1') d = d.substring(1);
if (d.length() < 6) return Optional.empty();
return Optional.ofNullable(index.get(d.substring(0, 6)));
}
private static String blank(String v) {
return (v == null || v.isEmpty()) ? null : v;
}
private static double parse(String v) {
try { return Double.parseDouble(v); } catch (Exception e) { return 0; }
}
private static String[] split(String line) {
List<String> out = new ArrayList<>(32);
StringBuilder sb = new StringBuilder();
boolean q = false;
for (int i = 0; i < line.length(); i++) {
char ch = line.charAt(i);
if (q) {
if (ch == '"' && i + 1 < line.length()
&& line.charAt(i + 1) == '"') { sb.append('"'); i++; }
else if (ch == '"') q = false;
else sb.append(ch);
} else if (ch == '"') q = true;
else if (ch == ',') { out.add(sb.toString()); sb.setLength(0); }
else sb.append(ch);
}
out.add(sb.toString());
return out.toArray(new String[0]);
}
}
Spring Boot: load this once as a
@Bean singleton at startup, not per request. For anything larger than a
single service, import into your database and query it there instead.
These are deliberately dependency-free. Use a real CSV library in production — the hand-rolled splitters above are here so the parsing quirks are visible, not because they are the best tool.
See every column Look at real rows firstEverything worth knowing before you buy. Deeper implementation questions — import quirks, field semantics, format differences — live on the technical FAQ.
Each edition offers different levels of data depth:
NPA-NXX (also called NPANXX or Area Code Database) represents North American telephone numbering assignments. NPA is the 3-digit area code (like 212), and NXX is the 3-digit exchange prefix (like 555). Together they identify the first 6 digits of a 10-digit phone number and determine its geographic location, carrier, and routing information.
This data is essential for:
Unlike individual phone number lookups, NPA-NXX data gives you the infrastructure intelligence behind the North American telephone network-no per-query fees, no rate limits, just complete control over your routing and validation logic.
Every purchase includes 12 months of quarterly updates at no additional cost. Four times per year, we synchronize with NANPA updates and release a fresh version of the complete database. Each update is a full file refresh-not incremental deltas-ensuring data consistency.
Access your updates two ways:
After 12 months, you keep all the data you've downloaded. Renewal is only needed if you want to continue receiving quarterly updates.
Timing: Updates are typically released mid-quarter (January, April, July, October). We send email notifications to all active subscribers when new releases are available.
Yes! You can upgrade at any time by simply paying the price difference. Your subscription end date remains the same, and you'll immediately get access to the additional 19 data fields in the Deluxe edition.
For example, if you purchased Standard ($249.95) and want to upgrade to Deluxe ($449.95), you'd pay $200 (the difference), and your 12-month update period stays unchanged. This is accessible directly from your Account Homepage. It will list all of your subscriptions and offer an immediate upgrade option. No waiting, the upgrade is instantaneous. Contact us if you have any further questions.
Every subscription includes:
You keep the data forever: After your subscription ends, you retain all downloaded files. Renewal is only required if you want continued quarterly updates.
Need redistribution rights? We offer separate licensing for embedding data in commercial products. Select Edition to see Redistribution pricing →
ZIP code boundaries and telephone exchange boundaries don't align perfectly-they were designed for different purposes (postal delivery vs. telecom infrastructure). Our database includes ZipCodeFreq rankings to show which ZIP codes are most commonly associated with each NPA-NXX:
We also include ZipCodeCount (population estimate) to help you assess reliability. ZIP codes with less than 2% frequency are excluded as statistically insignificant.
Best practices: For precise applications, use the -1 ranked ZIP codes, which represent the primary service area. For comprehensive coverage, include -2 ranked codes as well. The combination provides excellent geographic attribution for most business applications.
Our database is derived directly from NANPA (North American Numbering Plan Administration)-the official authority for area code and exchange assignments. This is the same authoritative source used by all telecom carriers in North America.
Our quality process:
Accuracy considerations: While NANPA assignment data is definitive, some fields reflect point-in-time snapshots. Carrier assignments can change (especially with number portability), and new NPA-NXX blocks are issued monthly. Our quarterly updates keep you current within a 3-month window, which is sufficient for most business applications.
Bottom line: For NPA-NXX assignments, geographic locations, and rate center data, this is the most authoritative source available outside of direct carrier records.
The NXXUseType field (L=Landline, W=Wireless) is derived from NANPA's official assignments and reflects the original carrier type when the NPA-NXX block was assigned. This data is accurate at the block level.
Important caveats:
Best uses:
For critical applications requiring definitive real-time wireless/landline identification (like TCPA compliance), consider supplementing with a real-time lookup service that checks current line type and portability status.
Rate centers are geographic boundaries originally created to determine local calling areas and billing zones. Each NPA-NXX is assigned to a specific rate center, which defines its "home" location within the telephone network.
Why rate centers still matter:
Common applications:
Available in: Deluxe edition only. Standard edition does not include rate center assignments.
The Status field indicates whether an NPA-NXX combination is currently in service:
Why we include inactive records:
Best practices:
Status='Active' onlyAvailable in: Deluxe edition only. Standard edition does not include status tracking.
OCN (Operating Company Number) is a unique 4-character identifier assigned by NECA (National Exchange Carrier Association) to each telecommunications carrier. The Company field shows the human-readable carrier name.
Key differences:
| Attribute | OCN | Company Name |
|---|---|---|
| Format | 4 characters (e.g., "9206") | Variable text (e.g., "Verizon New Jersey, Inc.") |
| Uniqueness | One OCN = One carrier entity | Names may have variations |
| Stability | Rarely changes | Can change with rebranding |
| Best for | Database keys, joins, matching | User display, reporting |
Practical usage:
Example: Verizon has multiple OCNs for different regions (9206 for New Jersey, 9740 for Pennsylvania). OCN distinguishes these entities while company names might appear similar.
Every purchase includes all three formats at no additional cost:
Database compatibility:
LOAD DATA INFILE for fast CSV importsCOPY command for efficient bulk loadingBULK INSERT or Import/Export Wizard.import commandAll formats contain identical data-choose based on your import workflow and tooling preferences. Download all three formats if you want flexibility.
Yes! For validation of area code + exchange (first 6 digits), this database is perfect. Simply extract the first 6 digits (NPA-NXX) from any phone number and look it up.
What you can validate:
Validation example:
Phone: (212) 555-1234
Extract: 212555
Lookup: NPA=212, NXX=555
Result: Valid block, New York City, Landline, Verizon
Important limitations:
Performance: With proper database indexing on NPANXX or (NPA, NXX) columns, lookups complete in milliseconds. Perfect for real-time validation in web forms or batch processing millions of records.
Yes! Our data works with any system that can import CSV, TAB, or MDB files. The data is normalized, consistently formatted, and ready for production use.
Common integration patterns:
Integration example (SQL):
-- Enrich customer table with area code data
UPDATE customers c
JOIN npanxx n ON SUBSTRING(c.phone, 1, 6) = n.NPANXX
SET c.phone_state = n.State,
c.phone_timezone = n.TimeZone,
c.phone_carrier = n.Company;
Implementation time: Most customers complete initial integration in under a day. The standardized format eliminates parsing complexity.
Bonus: Many customers combine our Area Code Database with our ZIP Code Database for complete geographic and demographic intelligence-join on the ZIP code field to enrich phone data with Census information and precise coordinates.
We offer two update delivery methods for your convenience:
1. Automated FTP/SFTP/FTPS (Recommended for production):
2. Manual download from your account:
Update strategy recommendations:
-- MySQL example
RENAME TABLE
npanxx_active TO npanxx_backup,
npanxx_new TO npanxx_active;
Import tips: Use your database's native CSV import tools (MySQL's LOAD DATA INFILE, PostgreSQL's COPY, SQL Server's BULK INSERT). The CSV format is standardized and imports cleanly into all major databases. Index on NPANXX, NPA, NXX, State, and ZipCode fields for optimal query performance.
It depends on how you're using the data:
Standard subscription is sufficient if you're:
You need a redistribution license if you're:
Redistribution license benefits:
Still unsure? Contact us with your use case-we'll help determine which license type you need. We want you to use the data confidently and compliantly.
We offer a 30-day, no-questions-asked money-back guarantee. If the data doesn't meet your needs for any reason, just contact us within 30 days of purchase for a full refund.
Why we're confident offering this:
No risk, no hassle. We stand behind our data quality because we know it works. Try it risk-free and see for yourself why thousands of customers trust ZIP-Codes.com for their telecom data needs.
While NANPA provides raw assignment files for free, our database offers production-ready enhancements that save you significant time and effort:
| Feature | Free NANPA Files | Our Database |
|---|---|---|
| Data Format | Pipe-delimited text files requiring custom parsing | Clean CSV, TAB, and MDB formats-import in minutes |
| ZIP Code Correlations | Not provided | ZIP code matching with frequency rankings and population data |
| Geographic Enrichments | Basic assignment data only | Coordinates, time zones, DST flags, county data, MSA/CBSA mapping (Deluxe) |
| Maintenance | You download, process, normalize, and QA every quarter | We handle all updates, corrections, quality assurance, and formatting |
| Data Cleaning | Raw data with inconsistencies | Normalized, validated, and production-ready |
| Historical Tracking | Current snapshot only | Status tracking (Active/Inactive), version history, change documentation |
| Support | No implementation help | Email and phone support for integration questions |
| Delivery | Manual download from government site | Instant download + optional automated FTP/SFTP delivery |
Time savings: Most customers report saving 8-12 hours per quarter compared to processing NANPA files manually. That's parsing pipe-delimited formats, handling encoding issues, normalizing carrier names, adding geographic references, validating data integrity, and building import scripts.
The bottom line: NANPA data is authoritative, but it's raw infrastructure data designed for carriers, not developers. We transform it into production-ready intelligence with the enrichments and formatting your applications actually need.
Your choice: Spend hours every quarter wrestling with government data formats, or spend $249-$449 annually and focus on building your application. Explore our live data to see the difference.
Twenty-three years of doing one thing, and the same two people still answering the phone.
Eddie Harris
Lead Data Architect - 23 Years Experience
Area code data isn't static — NPA splits, overlays, and number-pool reassignments happen multiple times a year. Eddie and our team track every NANPA bulletin and FCC filing, then cross-validate against carrier rate-center records before each release. A bot scraping a Wikipedia page won't catch a planned 2026 overlay before it goes live; we will.
When a rate center swaps carriers or an NPA boundary shifts mid-year, we trace each NXX prefix back to source and confirm jurisdiction. That's the work behind the dataset.
More About Our Team →
Our mission with area code data: give your call-routing, fraud-detection, and territory-mapping systems the NPA-NXX precision they need — including the planned splits, overlays, and rate-center changes that are about to happen, not just what shipped last quarter.
Since 2003 we've followed NANPA bulletins, FCC rulings, and individual carrier rate-center filings. Every quarterly release is reconciled against active carrier data so the geographic centroid of NPA 415 reflects what your callers actually see today, not the 2010 boundary.
When you're integrating area-code data into a phone system, a CRM, or a regulatory compliance flow, the wrong NXX-to-location mapping is a real operational problem. When you call us about it, you'll reach someone who knows what an OCN is, not a chatbot reading a help article.
That is what our customers say. Every edition downloads the moment you buy, and is covered for 30 days.
See pricing & editionsWhere every column comes from, and what we do to it before it reaches you.
Our sources include trusted federal data from NANPA (North American Numbering Plan Administration), USPS ZIP Code data, Canada Post postal code data, and proprietary data enhancements created by ZIP-Codes.com. Data last updated on June23, 2026.
NANPA data. NPA-NXX assignment data is derived from official NANPA (North American Numbering Plan Administration) records. NANPA is responsible for the administration and assignment of area codes and central office codes throughout the North American Numbering Plan. This database is independently compiled and is not affiliated with or endorsed by NANPA.
USPS data. ZIP Code™ is a trademark of the United States Postal Service. ZIP Code correlation data is derived from publicly available USPS geographic information.
Canada Post data. Postal Code™ is a trademark of Canada Post Corporation. Canadian reference data is used in accordance with applicable licensing terms.