Google Maps vs Mapbox for Address Validation

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.