A local business exists online in dozens of places it does not control: map apps, review sites, industry directories, data aggregators, social profiles. Each one holds its own copy of the business’s name, address and phone number, known in local SEO as NAP. When the business moves, changes its number or rebrands, most of those copies stay as they were.
The result is a customer calling a disconnected number, a delivery driver at the old address, and search engines and map apps holding conflicting facts about the same business. Many smaller sites copy their data from larger ones, so one wrong listing tends to spread.
A NAP audit finds those inconsistencies systematically. This guide covers what to collect, how to compare listings so formatting noise does not drown real problems, the code to do it, and how to prioritise fixes.
What a NAP audit checks
For each place a business is listed, an audit answers three questions:
- Name: is it the business’s real name, as used on its storefront and website, without added keywords or an old brand?
- Address: is it the current address, including the suite or unit, with the right postal code?
- Phone: is it the current main number, rather than an old line or a tracking number nobody answers?
Many audits also check the website URL, opening hours and primary category, since those go stale the same way. The method below extends to them easily.
Step 1: write down the canonical record
Everything is compared against one record: the business’s NAP as it should appear everywhere. Take it from the business itself, not from a directory, and write it in full: legal suffixes dropped, the street address exactly as the post office has it, the main phone number. For a multi-location business, keep one canonical record per location, with a stable ID.
This sounds obvious, and it is where most audits go wrong. If the canonical record is itself out of date, every comparison is wrong in the same direction.
Step 2: collect the listings
The listings to collect fall into three groups:
| Source | Examples | How to collect |
|---|---|---|
| Major platforms | Google Business Profile, Apple Business Connect, Bing Places, Facebook | The business’s own dashboards and official APIs |
| Review and directory sites | General and industry directories, review platforms | Official APIs where they exist, otherwise the public listing page |
| Data aggregators | The companies that supply many smaller directories | Their business portals |
Use official APIs and the business’s own accounts wherever they exist: they are more reliable and they are permitted. For directories without an API, the listing page is public, and collecting one page per business per directory is a light, occasional task. Listings can also differ by country and city, so check them as a local customer would see them; our guide to collecting local business data covers the collection side.
Record the raw values exactly as listed, along with the URL and the date. The audit compares normalised values, but the fix needs to know exactly what is wrong on each site.
Step 3: compare without drowning in noise
A naive comparison flags almost everything. “St” against “Street”, “(312) 555-0142” against “312-555-0142”, “LLC” or no “LLC”: none of these are real problems, and an audit full of them hides the listing that still has last year’s phone number.
The fix is to normalise each field before comparing, and to classify the result:
| Status | Meaning | Priority |
|---|---|---|
| match | Identical to the canonical record | None |
| format | Different text, same value after normalising | Low: tidy up when convenient |
| close | Nearly the same, such as a missing suite number or a typo | Medium: check and fix |
| mismatch | A different value: another address, another number, another name | High: fix first |
| missing | The field is empty on that listing | Medium: add it |
The code below normalises each field in a way that suits it. Names drop punctuation, accents, a leading “The” and legal suffixes. Addresses expand to standard abbreviations (“Street” to “st”, “Suite” to “ste”) and drop the ZIP+4 extension. Phone numbers are parsed with phonenumbers, the Python port of Google’s phone number library, and compared in international format, so any formatting of the same number matches.
import re
import unicodedata
from difflib import SequenceMatcher
import phonenumbers
LEGAL_SUFFIXES = {"llc", "inc", "ltd", "co", "corp", "gmbh", "plc", "sl", "srl"}
ADDRESS_WORDS = {
"street": "st", "avenue": "ave", "boulevard": "blvd", "road": "rd", "drive": "dr",
"lane": "ln", "court": "ct", "place": "pl", "suite": "ste", "floor": "fl",
"north": "n", "south": "s", "east": "e", "west": "w", "apartment": "apt", "unit": "ste",
}
def _words(text):
text = unicodedata.normalize("NFKD", text).encode("ascii", "ignore").decode().lower()
text = text.replace("&", " and ").replace("#", " ste ")
return re.sub(r"[^a-z0-9 ]+", " ", text).split()
def norm_name(name):
words = [w for w in _words(name) if w not in LEGAL_SUFFIXES]
return " ".join(words[1:] if words[:1] == ["the"] else words)
def norm_address(address):
# Drop the ZIP+4 extension, so "60607-1234" matches "60607".
address = re.sub(r"\b(\d{5})-\d{4}\b", r"\1", address)
return " ".join(ADDRESS_WORDS.get(w, w) for w in _words(address))
def norm_phone(phone, region="US"):
try:
number = phonenumbers.parse(phone, region)
except phonenumbers.NumberParseException:
return None
if not phonenumbers.is_valid_number(number):
return None
return phonenumbers.format_number(number, phonenumbers.PhoneNumberFormat.E164)
def compare(field, canonical, listed, region="US"):
"""'match', 'format' (same after normalising), 'close', 'mismatch' or 'missing'."""
if not listed:
return "missing"
if listed.strip() == canonical.strip():
return "match"
if field == "phone":
a, b = norm_phone(canonical, region), norm_phone(listed, region)
return "format" if a and a == b else "mismatch"
normalise = norm_name if field == "name" else norm_address
a, b = normalise(canonical), normalise(listed)
if a == b:
return "format"
return "close" if SequenceMatcher(None, a, b).ratio() >= 0.9 else "mismatch"
def audit(canonical, listings, region="US"):
"""One row per directory: the status of name, address and phone."""
rows = []
for source, listing in listings.items():
row = {"source": source}
for field in ("name", "address", "phone"):
row[field] = compare(field, canonical[field], listing.get(field), region)
rows.append(row)
return rows
The region argument tells the phone parser which country to assume for numbers written without a country code, so the same code works for a German or Brazilian business with region="DE" or region="BR". Address abbreviations are English-language; extend ADDRESS_WORDS for other markets.
Running an audit
Here is an audit of one business against four directory listings (an illustrative example, not real listings):
from nap import audit
canonical = {
"name": "Corner Bakery",
"address": "123 North Main Street, Suite 4, Chicago, IL 60607",
"phone": "(312) 555-0142",
}
listings = {
"Directory A": {"name": "Corner Bakery", "address": "123 N Main St #4, Chicago IL 60607",
"phone": "312-555-0142"},
"Directory B": {"name": "The Corner Bakery LLC", "address": "123 North Main Street, Suite 4, Chicago, IL 60607",
"phone": "(312) 555-0142"},
"Directory C": {"name": "Corner Bakery", "address": "88 West Lake Street, Chicago, IL 60601",
"phone": "(312) 555-0199"},
"Directory D": {"name": "Corner Bakery & Cafe", "address": "123 N Main St, Chicago, IL 60607"},
}
print(f"{'source':<12} {'name':<9} {'address':<9} phone")
for row in audit(canonical, listings):
print(f"{row['source']:<12} {row['name']:<9} {row['address']:<9} {row['phone']}")
source name address phone
Directory A match format format
Directory B format match match
Directory C match mismatch mismatch
Directory D mismatch close missing
Reading it row by row:
- Directory A formats the address and phone differently, but every value is correct. Nothing to fix.
- Directory B adds “The” and “LLC” to the name. Also fine, though worth tidying.
- Directory C has the right name with an old address and an old phone number. This is the listing that sends customers to the wrong place, and the one to fix first.
- Directory D has a different name (“Corner Bakery & Cafe”), is missing the suite number and has no phone at all.
Step 4: fix in the right order
- Mismatches on major platforms first. These listings reach the most people, and many smaller sites copy from them.
- Mismatches everywhere else, starting with the directories most relevant to the business’s industry and the ones that rank in search for the business’s name.
- Missing fields, especially the phone number.
- Close matches such as a missing suite number.
- Format differences last, if at all.
Some listings can be edited directly through the business’s own account. Others need a claim process or a support request, and data aggregators often need corrections submitted through their own portals, after which the change flows to the sites they supply. Expect fixes on some directories to take weeks to appear.
Step 5: re-audit on a schedule
Listings drift back. Aggregators can overwrite corrections, users can suggest edits on some platforms, and old data resurfaces from sources nobody remembered. Re-run the audit monthly for the major platforms and quarterly for the long tail, and always after a move, a phone change or a rebrand.
Keep each audit’s results with its date. The trend matters more than any single run: a listing that flips back to the old address after every correction points to a source upstream that still holds the wrong data.
For businesses where map rankings matter, pair the audit with rank measurement across the area the business serves. Our guide to geo-grid rank tracking shows how to measure that, and how to tell whether visibility improves after the listings are cleaned up.
Common causes of inconsistent NAP
- Moves and phone changes that were updated on the main profile but nowhere else.
- Call-tracking numbers added for an advertising campaign and never removed.
- Rebrands where the old name survives on long-tail directories.
- Suites and unit numbers dropped by directories that have no field for them.
- Duplicate listings, often created automatically, each with its own slightly different data.
- Multi-location businesses where one location’s data was copied onto another’s listing.
FAQ
What is NAP consistency?
NAP stands for name, address and phone number. NAP consistency means a business’s NAP is the same, and correct, everywhere it is listed online: map apps, directories, review sites and its own website.
Do formatting differences like “St” and “Street” matter?
Much less than wrong data. An audit should normalise formatting before comparing, so that the listings with an old address or phone number stand out instead of being lost among harmless differences.
How often should I audit business listings?
Monthly for the major platforms and quarterly for other directories is a reasonable rhythm, plus an extra audit after any move, phone change or rebrand.
Should I use tracking phone numbers on directory listings?
Use them with care. A tracking number that is removed after a campaign leaves a listing with a dead number. If you use one, make sure the main number is listed too where the platform allows, and record which listings carry which number.
The bottom line
A NAP audit is a data quality job: one canonical record, every listing collected with its source and date, values normalised before they are compared, and results classified so the real problems stand out. Fix mismatches on the biggest platforms first, then work down, and re-audit on a schedule because listings drift. The code above handles the comparison; the discipline is keeping the canonical record right and acting on what the audit finds.