For deliverability-grade address validation choose Google’s dedicated Address Validation API, which returns per-component verdicts and correction metadata; choose Mapbox permanent geocoding when you primarily need a storable, low-cost coordinate and can derive a validity signal from relevance and match_code yourself — both approaches are framed within the wider comparing geocoding accuracy across providers guide.
Decision Matrix
Validation is a different problem from geocoding. Geocoding asks “where is this address?”; validation asks “is this address real, complete, and deliverable, and what is the corrected form?” Google ships a purpose-built product for the second question, while Mapbox answers it indirectly through its geocoding response. That distinction drives the multi-API routing decision below.
| Axis | Google Address Validation API | Mapbox Geocoding (permanent) |
|---|---|---|
| Purpose | Dedicated address verification | Geocoding, with validity signals |
| Component verdicts | Per-component confirmationLevel |
match_code per component (v6) |
| Correction output | Standardized + inferred/spell-fixed components | Normalized place_name string |
| Missing-part flags | missingComponentTypes, unresolvedTokens |
Inferred from match_code |
| Deliverability hint | US CASS metadata via USPS data | No native deliverability verdict |
| Storage rights | Restricted under Maps Platform terms | Permanent scope permits storage |
| Cost basis | Per-1,000 validation calls | Per-1,000; permanent tier priced higher |
Component-Level Confidence
The reason a dedicated validation product exists is granularity. Google’s Address Validation API returns an addressComponents array where each part — street number, route, locality, postal code — carries a confirmationLevel of CONFIRMED, UNCONFIRMED_BUT_PLAUSIBLE, or UNCONFIRMED_AND_SUSPICIOUS. It separately reports missingComponentTypes (parts the address should have but does not), unconfirmedComponentTypes, and unresolvedTokens (input fragments it could not place). Crucially it also flags whether it inferred or spellCorrected a component, so you can distinguish a clean confirmation from one Google had to repair. That is exactly the signal an order-entry or billing system needs before it accepts an address.
Mapbox does not expose a verdict of this shape. Its v6 geocoding response includes a match_code object that reports, per component, whether each matched, was inferred, or was not found, alongside the overall relevance float. You can synthesize a validation decision from those fields, but you are building the rule yourself rather than reading a verdict. For US mailing deliverability specifically, Google’s validation output can surface USPS-derived standardization that Mapbox has no equivalent for — relevant if you are working toward the postal-standard rules described in the USPS CASS certification guidelines.
Storage Rights and Cost
The commercial trade-off inverts the accuracy one. Google’s richer verdict comes under Maps Platform terms that restrict storing results, so you typically validate at capture time and persist only your own derived boolean, not Google’s payload. Mapbox sells a permanent geocoding tier explicitly licensed for storing coordinates indefinitely — priced above its temporary tier, but it removes the legal blocker on caching. If your pipeline must retain normalized coordinates for reuse, Mapbox’s permanent scope is architecturally simpler; if you need the deepest per-component verdict at the moment of capture and can discard the raw response, Google’s Address Validation API is stronger. Re-read both providers’ current terms before you commit; these clauses change and control your caching design.
Unified Python Client
The client calls either provider and normalizes both into one validation verdict, collapsing Google’s per-component confirmationLevel and Mapbox’s match_code into a shared component_status map plus an overall is_deliverable_guess.
from __future__ import annotations
import logging
from dataclasses import dataclass, field
from typing import Optional
import requests
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logger = logging.getLogger(__name__)
# Collapse provider verdicts onto a shared 3-state status.
_GOOGLE_LEVEL: dict[str, str] = {
"CONFIRMED": "confirmed",
"UNCONFIRMED_BUT_PLAUSIBLE": "plausible",
"UNCONFIRMED_AND_SUSPICIOUS": "suspicious",
}
_MAPBOX_CODE: dict[str, str] = {
"matched": "confirmed",
"inferred": "plausible",
"unmatched": "suspicious",
"not_applicable": "plausible",
}
@dataclass
class ValidationVerdict:
"""Unified address-validation result across providers."""
formatted: str
lat: float
lon: float
component_status: dict = field(default_factory=dict) # component -> status
missing_components: list = field(default_factory=list)
corrected: bool = False
provider: str = ""
raw_input: str = ""
@property
def is_deliverable_guess(self) -> bool:
"""Heuristic: no suspicious component and nothing structurally missing."""
statuses = set(self.component_status.values())
return "suspicious" not in statuses and not self.missing_components
def parse_google_validation(address: str, payload: dict) -> ValidationVerdict:
"""Parse a Google Address Validation API response."""
result = payload.get("result", {})
addr = result.get("address", {})
geocode = result.get("geocode", {})
location = geocode.get("location", {})
components = {}
corrected = False
for comp in addr.get("addressComponents", []):
ctype = comp.get("componentType", "unknown")
components[ctype] = _GOOGLE_LEVEL.get(
comp.get("confirmationLevel", ""), "plausible"
)
if comp.get("inferred") or comp.get("spellCorrected"):
corrected = True
return ValidationVerdict(
formatted=addr.get("formattedAddress", ""),
lat=float(location.get("latitude", 0.0)),
lon=float(location.get("longitude", 0.0)),
component_status=components,
missing_components=list(addr.get("missingComponentTypes", [])),
corrected=corrected,
provider="google",
raw_input=address,
)
def parse_mapbox_validation(address: str, payload: dict) -> ValidationVerdict:
"""Parse a Mapbox v6 geocoding feature into a validation verdict."""
features = payload.get("features", [])
if not features:
raise ValueError("Mapbox returned no results")
top = features[0]
props = top.get("properties", {})
lon, lat = top.get("geometry", {}).get("coordinates", [0.0, 0.0])
match_code = props.get("match_code", {})
components = {
part: _MAPBOX_CODE.get(state, "plausible")
for part, state in match_code.items()
if part not in {"confidence"}
}
missing = [p for p, s in match_code.items() if s == "unmatched"]
return ValidationVerdict(
formatted=props.get("full_address", props.get("name", "")),
lat=float(lat),
lon=float(lon),
component_status=components,
missing_components=missing,
corrected=any(s == "inferred" for s in match_code.values()),
provider="mapbox",
raw_input=address,
)
def validate(address: str, provider: str, api_key: str) -> ValidationVerdict:
"""Validate one address via Google or Mapbox and normalize the verdict."""
if provider == "google":
url = "https://addressvalidation.googleapis.com/v1:validateAddress"
params = {"key": api_key}
body = {"address": {"addressLines": [address]}}
resp = requests.post(url, params=params, json=body, timeout=10)
resp.raise_for_status()
return parse_google_validation(address, resp.json())
if provider == "mapbox":
url = "https://api.mapbox.com/search/geocode/v6/forward"
params = {"q": address, "access_token": api_key,
"limit": 1, "permanent": "true"}
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
return parse_mapbox_validation(address, resp.json())
raise ValueError(f"Unsupported provider: {provider!r}")
Vectorized pandas batch
Validation is usually run once at capture, but backfilling a customer table is a batch job. The wrapper keeps failed rows so you can re-drive only the residue.
import pandas as pd
def validate_frame(
df: pd.DataFrame,
address_col: str,
provider: str,
api_key: str,
) -> pd.DataFrame:
"""Validate every row and append verdict columns, retaining failures."""
def _safe(addr: str) -> dict:
try:
v = validate(addr, provider, api_key)
row = v.__dict__.copy()
row["is_deliverable_guess"] = v.is_deliverable_guess
return row
except (ValueError, requests.RequestException) as exc:
logger.warning("validation failed for %r: %s", addr, exc)
return {"provider": provider, "raw_input": addr,
"is_deliverable_guess": False}
expanded = df[address_col].map(_safe).apply(pd.Series)
return df.join(expanded, rsuffix="_val")
Edge Cases
Google inferred components can mask a bad input
When Google returns CONFIRMED components that were inferred or spellCorrected, the address validates but is not what the customer typed — a silently corrected postal code can route mail to the wrong ZIP. The corrected flag on the verdict above surfaces this so you can prompt the user to confirm the standardized form rather than accepting it blindly.
Mapbox match_code without a deliverability guarantee
Mapbox’s match_code tells you which components matched geographically, not whether the address is a real deliverable point. A vacant lot or an under-mapped new development can return matched components yet not be a valid mailing address. Treat the Mapbox verdict as a plausibility gate and escalate uncertain records to a stricter check through a fallback chain for failed lookups.
Integration Note
The two providers combine well: use Mapbox permanent geocoding as the storable primary that populates your coordinate cache, and call Google’s Address Validation API only for records where Mapbox’s match_code reports an inferred or unmatched component and you need a deliverability-grade verdict — discarding Google’s raw payload after deriving your own boolean. Anchor the threshold at which you escalate to the confidence methodology in the parent comparing geocoding accuracy across providers guide, and let a region-aware provider selector decide which validator leads by country.
Related
- Comparing Geocoding Accuracy Across Providers — the benchmarking method for turning validation verdicts into calibrated accept/reject thresholds.
- Dynamic Provider Selection Based on Region — route Google or Mapbox validation first depending on the destination country.
- Implementing Fallback Chains for Failed Lookups — escalate suspicious or incomplete validations to a stricter provider automatically.
- USPS CASS Certification Guidelines — the postal standard behind US deliverability and standardization signals.