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

Validation Asks a Different Question Than Geocoding

A geocoder answers “where is this”; a validator answers “is this real and deliverable”. Both providers will return a coordinate for a plausible-looking address that does not exist, because interpolation along a street segment is a legitimate geocoding answer and a useless validation answer. Knowing which question you are asking determines which fields you read.

Two questions, two sets of fields Two panels. The geocoding panel reads coordinates and a location type and is satisfied by an interpolated position. The validation panel reads component-level match flags and a delivery-point confirmation, and treats an interpolated position as an unvalidated result. A note explains that a validated address without a rooftop coordinate is often the correct outcome. Geocoding question "where should I put the pin?" reads: lat, lon, location_type an interpolated point is a valid answer misses: addresses that do not exist Validation question "will a parcel arrive here?" reads: per-component match flags an interpolated point proves nothing misses: nothing, if the flags are read A validated address with only a street-level coordinate is a normal, useful result — and a rooftop coordinate on an address whose house number was never confirmed is the dangerous one.

For checkout and onboarding flows, the validation reading is the one that matters, and it argues for treating component-level match information as the primary signal with the coordinate as a by-product. That is also why a certified postal validation service frequently belongs in front of either provider for US traffic — it answers the deliverability question directly rather than by inference.

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.

Component Flags Beat a Single Score

Both providers expose more than an overall score, and the per-component detail is where the actionable information lives. A record whose street matched but whose unit did not is a different customer conversation from one whose city and postcode disagree — and a single blended confidence number collapses both into “0.72”.

Component-level flags and the action each implies Four component rows for a single validated address. Street and house number matched exactly and need no action. The secondary unit was not confirmed, which should prompt the customer for an apartment number. The postcode was corrected by the provider, which should be surfaced for confirmation rather than applied silently. Component Result Action street name exact match none house number exact match none secondary unit not confirmed prompt for apartment or suite only postcode corrected show the change, ask to confirm

The bottom row is a policy decision as much as a technical one. Silently accepting a provider’s postcode correction is usually right and occasionally wrong in a way the customer would have caught instantly — a corrected postcode that moves the delivery to a different town. Surfacing the change costs one extra interaction and prevents the class of failure that is most expensive to unwind.

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")

Cost Behaviour at Different Volumes

The two providers price differently enough that the cheaper option flips as volume grows, and the flip point is worth computing before committing. Free tiers dominate the decision at small scale; per-call rates and storage terms dominate above roughly a hundred thousand lookups a month.

Which provider is cheaper depends on monthly volume Three volume bands compared. Below ten thousand monthly lookups both providers are effectively free and the decision should be made on developer experience. Between ten thousand and one hundred thousand, free-tier size dominates. Above one hundred thousand, the per-call rate and whether coordinates may be stored dominate the total cost. < 10 K / month both effectively free decide on SDK quality, docs and response shape cost is not the variable 10 K – 100 K free-tier size dominates a good cache can keep you inside the free band entirely optimise dedup before switching > 100 K / month per-call rate and storage terms dominate storage rights can outweigh the headline rate entirely negotiate, then re-measure Model the middle band with your real cache hit rate before switching provider — deduplication frequently moves a workload down a band, which is a larger saving than any per-call difference between the two.

That last observation generalises. Provider choice moves cost by tens of percent; deduplication and caching move it by an order of magnitude. Exhaust the local levers in caching and deduplication before spending engineering time on a migration between providers that are, on most corpora, within a few points of each other.

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.