Fix bad addresses with the Google Address Validation API

Learn how the Google Address Validation API parses, standardizes, and verifies global addresses. Includes pricing, examples, and use cases.

Fix bad addresses with the Google Address Validation API

As many as 20% of e-commerce packages fail to arrive on the first attempt* - often due to incorrect addresses. This leads to lost packages, frustrated customers, and higher costs in time, labor, and logistics. In this post, I’ll explain why address validation matters and how the Google Address Validation API can help you prevent these issues before they happen. We’ll also build a demo app (https://google-address-validation.afi.dev/) to show you how to call the Address Validation API and interpret its response. As always, you can find sample code for this project on GitHub.

Part 1: A practical guide to the Google Geocoding API
Part 2: Forward geocoding with the Google Geocoding API
Part 3: Google Maps reverse geocoding: Get addresses from coordinates
Part 4: Fix bad addresses with the Google Address Validation API (this article)
Part 5: Google Maps Building Outlines and Entrances API

* Harvard Business Review: Why so many packages don’t get delivered

Google Address Validation Tool
Validate and format addresses using Google’s Address Validation API. Get accurate address components, postal codes, and verification results.

What is the Google Address Validation API?

The Google Address Validation API is a web service that checks whether an address is deliverable - essentially answering the question: “If you mailed a postcard to this address, would it arrive?” Logistics companies care about deliverability because trying to deliver a package to a bad address is expensive. They have to absorb the cost of the failed attempt, plus the time and effort to contact the sender, fix the issue, and resend the package.

In the real world, addresses are messy and often incomplete
In the real world, addresses are messy and often incomplete

When should you use the google address validation API?

In the real world, addresses are often messy - missing ZIP codes, misspelled street names, and incorrect house numbers are common. Two main types of companies tend to use the Google Address Validation API:

  1. Third-Party Logistics Providers (3PLs):
    These are contracted delivery companies that receive shipments for last mile delivery. For example, Temu might pay a 3PL around $5 per package to deliver goods from their warehouse to the customer. If the 3PL fails to meet delivery standards - such as a 98% on-time rate within two days - they risk fines, chargebacks, or even contract termination.
  2. Direct Mail Distributors:
    These companies deliver newspapers, flyers, promotional materials, or any form of direct mail to a list of purchased addresses. Because they pay per address, regardless of whether it’s valid, they have a strong incentive to verify deliverability upfront to avoid wasting money on undeliverable mail.

What is the difference between geocoding and address validation?

Address geocoding converts an address into geographic coordinates (latitude and longitude). Services like the Google Geocoding API will return coordinates even if the address is inaccurate or incomplete. A result is returned so long as the address can be geocoded. There are no checks to ensure that the address is actually correct or deliverable.

Address validation, on the other hand, verifies whether an address is accurate and real. Google compares it against a database of known residential and business addresses and returns a possibleNextAction in it's verdict object to indicate how confident it is that the address is correct.

possibleNextAction Notes
FIX One or more fields of the API response indicate a potential issue with the post-processed address.
CONFIRM_ADD_SUBPREMISES There are minor issues with the address e.g. a unit number might be missing.
CONFIRM The address is generally correct but might have minor issues.
ACCEPT The address is considered deliverable.

Practically speaking, if the address returns anything other than ACCEPT, it should be treated with caution. It's best to contact the sender to review and confirm the details before proceeding.

As a bonus, the Address Validation API can also correct missing, incomplete, or malformed address components by returning a fully formatted, standardized version of the address - perfect for printing on shipping labels or displaying in your app. It also acts as a geocoder and provides geographic coordinates for the corrected address.

For example, if you input a partial address like “555 West Hastings St, Vancouver,” the API returns: “555 West Hastings Street, Vancouver, BC V6B 4N4, Canada” together with its precise latitude and longitude.

{
    "address": {
        "formattedAddress": "555 West Hastings Street, Vancouver, BC V6B 4N4, Canada",
        "postalAddress": {
            "regionCode": "CA",
            "languageCode": "en",
            "postalCode": "V6B 4N4",
            "administrativeArea": "BC",
            "locality": "Vancouver",
            "addressLines": [
                "555 W Hastings St"
            ]
        }
    },
    "geocode": {
        "location": {
            "latitude": 49.2846966,
            "longitude": -123.1119349
        }
    },
    "metadata": {
        "business": true,
        "residential": false
    }
}

Additional metadata - like whether the address is residential or a business, is also included.

Do you need the Google Address Validation API if you are already using Place Autocomplete?

Google address validation flow for e-commerce checkout
Google address validation flow for e-commerce checkout

Google promotes the Address Validation API as a tool to streamline the e-commerce checkout process by automatically correcting common address issues such as misspelled street names, missing ZIP codes, or incomplete entries. But if you are already using Place Autocomplete to verify the customer's entered address against Google Map's database, there's no need to use the Address Validation API to run a second round of validation because the address retrieved from Place Autocomplete is guaranteed to exist.

Moreover, a positive result from the Google Address Validation API does not guarantee a successful delivery. Even if the API confirms that an address is valid, omitting critical details such as a unit or apartment number in a multi unit building can still lead to failed deliveries or returns as the courier won’t know where to deliver the package. To avoid this, it's essential to make the unit or house number field a required part of your checkout form.

Google Address Validation API pricing

Like other Google Maps Platform APIs, the Address Validation API uses tiered pricing based on usage volume. Customers are billed according to the number of addresses submitted each month. The first 5,000 requests are free, and after that, the cost per 1,000 addresses (CPM) decreases as your monthly volume increases.

Monthly addresses Cost per 1,000 addresses
Up to 5,000 Free
5,000 to 100,000 $17.00 CPM
100,000 to 500,000 $13.60 CPM
500,000 to 1 mil $10.20 CPM
1 mil - 5 mil $5.10 CPM
5 mil + $1.28 CPM

Google Address Validation API coverage

The Google Address Validation API supports over 240 countries and territories, but coverage quality varies by region (see full list here). In general:

Highest Coverage (High confidence, deliverability checked)

  • 🇺🇸 United States,
  • 🇨🇦 Canada,
  • 🇬🇧 United Kingdom, 🇦🇺 Australia, 🇩🇪 Germany and 🇯🇵 Japan.

Moderate Coverage:

  • Many countries in Europe, Southeast Asia e.g. 🇸🇬 Singapore, and South America.
  • Some addresses (particularly in rural areas) have missing meta data or do not exist in Google's database.

Limited Coverage:

  • Rural or less-developed areas in Africa, South Asia, or Latin America.

Broadly speaking, Google prioritizes address coverage based on user demand and revenue potential. In many less developed countries, address validation is less of a priority - partly because many businesses can’t afford premium validation tools, and partly because the low cost of labor makes redelivery attempts relatively inexpensive.

Google Address Validation API key

To enable the Google Address Validation API in your Google Cloud Console, follow these steps:

  1. Create a new project at https://console.cloud.google.com/ by clicking the project dropdown in the top navigation bar (next to the Google Cloud logo) and name the project address-validation-api-demo. Click [CREATE].
Create a new GCP project. to test the Google Address Validation API
Create a new GCP project. to test the Google Address Validation API
  1. To enable the Address Validation API, navigate to the APIs & Services page by selecting it from the menu on the left. Then, click [+ Enable APIs and Services] and search for "Address Validation API".
Searching for the correct API to enable
Searching for the correct API to enable
  1. Click on the [Address Validation API] link and hit the [Enable] button.
Enabling the Google Address Validation API key
Enabling the Google Address Validation API key

After that you'll see your new API key in a popup window. Click [Go to Google Maps Platform] to return to the main dashboard. If you return to the Keys and Credentials page, you can find your new API key by clicking the [Show Key] link on the right.

Your Google Maps API key with address validation enabled
Your Google Maps API key with address validation enabled

Google Address Validation API example

Here's a simple example that shows how address validation works for a partial address "555 W Hastings St" for an office building in downtown Vancouver, Canada.

Endpoint POST https://addressvalidation.googleapis.com/v1:validateAddress?key={YOUR_API_KEY}

Headers
Content-Type: application/json;

Request Body

{
  "address": {
    "regionCode": "CA",
    "locality": "Vancouver",
    "addressLines": ["555 West Hastings St"]
  }
}

The request body contains a single field, address, which represents a postal address suitable for mail delivery—such as a street address, building, or P.O. box. It is not meant to describe general geographic locations like roads, towns, or landmarks. Inside the address object, you can include a:

  • regionCode, the two letter code used by Google Maps to identify countries or regions e.g. "CA" for Canada (see full list of supported codes). It is recommended to include the region code if you know it, but make sure that your address does not contain contradictions e.g. setting the regionCode to Canada for a US address.
  • locality, the city or town the address is located in e.g. "Vancouver" or "Seattle". In regions of the world where localities are not well defined or do not fit into this structure well, leave locality empty.
  • addressLines, an array of strings that together form the complete street address for mailing or display purposes. In most cases it will contain a single object with the partial address you want to validate.

Response

{
    "result": {
        "verdict": {
            "inputGranularity": "PREMISE",
            "validationGranularity": "PREMISE",
            "geocodeGranularity": "PREMISE",
            "addressComplete": true,
            "hasInferredComponents": true,
            "possibleNextAction": "ACCEPT"
        },
        "address": {
            "formattedAddress": "555 West Hastings Street, Vancouver, BC V6B 4N4, Canada",
            "postalAddress": {
                "regionCode": "CA",
                "languageCode": "en",
                "postalCode": "V6B 4N4",
                "administrativeArea": "BC",
                "locality": "Vancouver",
                "addressLines": [
                    "555 W Hastings St"
                ]
            },
            "addressComponents": [
                {
                    "componentName": {
                        "text": "555"
                    },
                    "componentType": "street_number",
                    "confirmationLevel": "CONFIRMED"
                },
                {
                    "componentName": {
                        "text": "West Hastings Street",
                        "languageCode": "en"
                    },
                    "componentType": "route",
                    "confirmationLevel": "CONFIRMED"
                },
                {
                    "componentName": {
                        "text": "Vancouver",
                        "languageCode": "en"
                    },
                    "componentType": "locality",
                    "confirmationLevel": "CONFIRMED"
                },
                {
                    "componentName": {
                        "text": "Canada",
                        "languageCode": "en"
                    },
                    "componentType": "country",
                    "confirmationLevel": "CONFIRMED"
                },
                {
                    "componentName": {
                        "text": "BC",
                        "languageCode": "en"
                    },
                    "componentType": "administrative_area_level_1",
                    "confirmationLevel": "CONFIRMED",
                    "inferred": true
                },
                {
                    "componentName": {
                        "text": "V6B 4N4"
                    },
                    "componentType": "postal_code",
                    "confirmationLevel": "CONFIRMED",
                    "inferred": true
                }
            ]
        },
        "geocode": {
            "location": {
                "latitude": 49.2846966,
                "longitude": -123.1119349
            },
            "plusCode": {
                "globalCode": "84XR7VMQ+V6"
            },
            "bounds": {
                "low": {
                    "latitude": 49.2844149,
                    "longitude": -123.1126507
                },
                "high": {
                    "latitude": 49.2851751,
                    "longitude": -123.111479
                }
            },
            "featureSizeMeters": 74.34052,
            "placeId": "ChIJUcKFZ3hxhlQREJGVU1foPaE",
            "placeTypes": [
                "premise"
            ]
        },
        "metadata": {
            "business": true,
            "residential": false
        }
    },
    "responseId": "7bb43d80-98f7-4fb1-9a43-17465a89ddc6"
}

The response is a result object that contains all the important data that tells you whether the address is valid, complete, and deliverable - and if so, what the standardized, corrected, and geocoded version of that address looks like. In the example above, the input "555 West Hastings St" was validated and standardized as "555 West Hastings Street, Vancouver, BC V6B 4N4, Canada." If you search for this address in Google Maps, it will appear exactly as returned by the Address Validation API.

The geocode returned by address validation is identical to the one on Google Maps
The geocode returned by address validation is identical to the one on Google Maps

In result,

verdict gives a high level overview of the address validation result and geocode:

  • verdict.inputGranularity provides detail level of the input (e.g. "PREMISE", "STREET" or "LOCALITY").
  • verdict.validationGranularity shows how precisely Google could validate the address e.g. "PREMISE" would indicate that Google has validated a building level result.
  • verdict.addressComplete is "TRUE" if there are no unresolved, unexpected or missing address components.
  • verdict.hasInferredComponents is "TRUE" if some parts of the returned complete address (e.g. province, postal code) were inferred by Google. In the example above, The city, province and zip code were all correct "guessed" by the address validation API.
  • verdict.possibleNextAction tells you what the recommended next step is e.g. "ACCEPT" suggests that you should accept the post-processed address as is.
Address validation verdict
Address validation verdict

address gives you a cleaned up, standardized version of the address, in both:

  • a formatted form, address.formattedAddress, that is ideal for display or printing,
Formatted address
Formatted address
  • a structured form, address.postalAddress, that breaks up the address into its constituent postal code, city, state and street.
Postal address
Postal address

It also shows a list of addressComponents, each with metadata like confirmation level and whether it was inferred.

Validated address components
Validated address components

geocode, the geocoded location.latitude and location.longitude of the address.

The metadata field indicates whether an address is classified as business (metadata.business) or residential (metadata.residential) - a distinction that's especially useful for scheduling deliveries during business hours.

Validated address metadata
Validated address metadata

Building a Google Address Validation API demo app

In this final section, we’ll build a demo app that uses the Address Validation API to check whether a single address is deliverable. Users can enter a partial address, choose a country, and click submit to run the validation.

It's closely modeled after the official example published by Google on their docs page (https://developers.google.com/maps/documentation/address-validation/demo). Since most address validation workflows involve processing multiple addresses on the backend, this app isn't super practical but it’s still useful for learning how to call the Google Address Validation API and understand its response.

Building your own Google Address Validation API demo app
Building your own Google Address Validation API demo app

google_address_validation is an interactive tool that lets you test how the Google Address Validation API works by validating a single address in real time. The app is a single-page application (SPA) built with React and set up using the Vite build tool.

If you don't want to pull the code from GitHub and run the app locally, you can play around with a live demo at https://google-address-validation.afi.dev/.

How our code is organized

Like many basic single-page React apps, google_address_validation uses a single App.jsx file as the central hub for most of the business logic. While this makes the file somewhat bloated and harder to maintain, it allows for quick development - ideal for a one off software project like this one.

Reusable UI components like <AddressForm/> and <ValidatedAddressTab/> are stored in a separate /components folder. These components are relatively “dumb,” meaning they rely entirely on props passed from App.jsx and don’t manage their own state or logic.

App.jsx

App.jsx is responsible for three main things:

  • Calling the Address Validation API and parsing the response,
  • Managing all application state using useState hooks to store API results and other values and,
  • Displaying results and rendering the UI by passing props to simple, stateless child components.

Calling the Address Validation API

The handleValidate() method in App.jsx makes a POST request to Google Address Validation API. It sends over the regionCode (e.g. "US" or "CA") and addressLines (the partial or full street address you want to validate), extracts the result field (which contains the validation output) and saves it.

/*** App.jsx ***/
const handleValidate = useCallback(async () => {
  if (!addressInput || regionInput === undefined || regionInput === null) {
    return;
  }

  setLoading(true);
  setApiError("");

  try {
    const res = await fetch(
      `https://addressvalidation.googleapis.com/v1:validateAddress?key=${import.meta.env.VITE_GOOGLE_API_KEY}`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          address: {
            regionCode: regionInput || "",
            addressLines: [addressInput],
          },
        }),
      },
    );

    if (!res.ok) {
      const text = await res.text();
      throw new Error(text || "Failed to validate address.");
    }

    const data = await res.json();
    const result = data.result || {};

  // Additional code that saves result to state
  
  } catch (error) {
    setApiError(
      error?.message ||
        "There was a temporary server error. Please try again later.",
    );
  } finally {
    setLoading(false);
  }
}, [addressInput, regionInput]);

Managing application state

To display the results of the address validation check, we start by creating useState hooks to store the key fields returned by the Address Validation API.

  • verdict, the high level outcome of the validation,
  • postalAddress, a cleaned and standardized version of the address, formatted according to local postal rules,
  • addressComponents, the parsed address, broken into standardized components,
  • metadata, additional context about the address
export default function App() {
  // API response data
  const [formattedAddress, setFormattedAddress] = useState("");
  const [verdict, setVerdict] = useState({
    inputGranularity: "GRANULARITY_UNSPECIFIED",
    validationGranularity: "GRANULARITY_UNSPECIFIED",
    geocodeGranularity: "GRANULARITY_UNSPECIFIED",
    hasUnconfirmedComponents: false,
    hasInferredComponents: false,
    hasReplacedComponents: false,
    addressComplete: false,
  });
  const [postalAddress, setPostalAddress] = useState({});
  const [components, setComponents] = useState([]);
  const [summary, setSummary] = useState({
    missingComponentTypes: [],
    unconfirmedComponentTypes: [],
    unresolvedTokens: [],
  });
  const [metadata, setMetadata] = useState({});
  const [usps, setUsps] = useState({});

  const handleValidate = useCallback(async () => {
    try {
      // API request code
      if (!res.ok) {
        // Result parsing copde
      }

      const data = await res.json();
      const result = data.result || {};

      setFormattedAddress(result?.address?.formattedAddress || "");
      setVerdict({
        inputGranularity:
          result?.verdict?.inputGranularity || "GRANULARITY_UNSPECIFIED",
        validationGranularity:
          result?.verdict?.validationGranularity || "GRANULARITY_UNSPECIFIED",
        geocodeGranularity:
          result?.verdict?.geocodeGranularity || "GRANULARITY_UNSPECIFIED",
        hasUnconfirmedComponents: !!result?.verdict?.hasUnconfirmedComponents,
        hasInferredComponents: !!result?.verdict?.hasInferredComponents,
        hasReplacedComponents: !!result?.verdict?.hasReplacedComponents,
        addressComplete: !!result?.verdict?.addressComplete,
      });
      setPostalAddress(result?.address?.postalAddress || {});
      setComponents(result?.address?.addressComponents || []);
      setSummary({
        missingComponentTypes: result?.address?.missingComponentTypes || [],
        unconfirmedComponentTypes:
          result?.address?.unconfirmedComponentTypes || [],
        unresolvedTokens: result?.address?.unresolvedTokens || [],
      });
      setMetadata(result?.metadata || {});
      setUsps(result?.uspsData || {});
    } catch (error) {
      setApiError(
        error?.message ||
          "There was a temporary server error. Please try again later.",
      );
    } finally {
      setLoading(false);
    }
  }, [addressInput, regionInput]);

  // Render code
}

After the response from the Address Validation API is stored in the result object, we use React's useState setter functions to update individual fields. For example, to update the formattedAddress, we call:
setFormattedAddress(result?.address?.formattedAddress || '');
This sets the value if it exists, or falls back to an empty string if it doesn't.

Displaying results and UI

The return statement in App.jsx renders the full address validation interface, structured into two main parts:

  1. An address input form (<AddressForm/>) and,
  2. The results display tab (<ValidatedAddressTab/>)

All of this is wrapped in styled <div> containers with CSS class names for layout and styling. Data from the response to the Address Validation API call are passed in as props e.g. formattedAddress, verdict, postalAddress, components, summary, metadata and usps and passed to the <ValidatedAddressTab/> component.

/*** App.jsx ***/
// Rest of App.jsx
return (
  <div className="validation-wrapper">
    <div className="container">
      <div className="title">
        <h1>Address Validation</h1>
      </div>

      <div className="app-shell">
        <AddressForm
          addressInput={addressInput}
          setAddressInput={setAddressInput}
          regionInput={regionInput}
          setRegionInput={setRegionInput}
          loading={loading}
          onValidate={handleValidate}
        />

        <ValidatedAddressTab
          loading={loading}
          apiError={apiError}
          formattedAddress={formattedAddress}
          verdict={verdict}
          postalAddress={postalAddress}
          components={components}
          summary={summary}
          metadata={metadata}
          usps={usps}
        />
      </div>
    </div>
  </div>
);

AddressForm.jsx

Address text input form
Address text input form

AddressForm.jsx is a simple text input form that lets the user enter an address, choose a country, and click a button to validate it.

To make it work, we first import a list of supported countries (see full list) from /constants/countries.js. This lets us populate the Region drop down menu.

/*** constants/countries.js ***/
export const COUNTRIES = [
  { value: "AR", label: "Argentina" },
  { value: "AU", label: "Australia" },
  { value: "AT", label: "Austria" },
  ... // 40 more countries
];

Next, we pass props from the parent App.jsx into the form component. Some of these props manage state - like addressInput, regionInput, and loading - so the page can update dynamically without reloading. Others, like onValidate, are functions used to submit the form data and trigger the Address Validation API call.

/*** components/AddressForm.jsx ***/
const AddressForm = ({
  addressInput,
  setAddressInput,
  regionInput,
  setRegionInput,
  loading,
  onValidate,
}) => {

The text input form itself is straightforward. The e.preventDefault() on the onSubmit event prevents the browser’s default behavior of reloading the page or navigating away when the form is submitted.

<form className="request-form" onSubmit={(e) => e.preventDefault()}></form>;

To make sure the data entered persists (i.e. does not disappear when we click submit), we bind the input field to addressInput and call setAddressInput whenever the user types something.

<input
  id="address"
  placeholder="Enter address..."
  value={addressInput}
  onChange={e => setAddressInput(e.target.value)}
/>

Finally, when the [Validate] button is clicked, it triggers the onValidate() function, which is connected to the handleValidate() method in App.jsx. This method sends the value from the address input field (addressInput) to the Address Validation API.

<Button onClick={onValidate} loading={loading}>
  Validate
</Button>

ValidateAddressTab.jsx

The verdict, postalAddress, components, and metadata returned by the Address Validation API are passed as props into ValidateAddressTab.jsx, where they are used to display the validation results. <ValidateAdressTab/> is a simple presentational component with various sections and child components laid out inside the UI:

ValidateAddressTab.jsx
└── <div class="tab-pane">
    ├── [Conditional] Alert (info)     ← if loading
    ├── [Conditional] Alert (warning)  ← if apiError
    ├── <FormattedAddress />           ← formatted address string
    ├── <VerdictSection />             ← validation flags & granularity
    │   └── <VerdictFlag /> (x4)       ← multiple flags (valid, warning, etc.)
    ├── <PostalAddressSection />       ← region, postal code, address lines
    ├── <AddressComponentsSection />   ← parsed address components
    │   └── <AddressComponents />      ← actual list display
    ├── <AddressComponentsSummarySection /> ← missing, unconfirmed, unresolved
    │   ├── Summary: Missing
    │   ├── Summary: Unconfirmed
    │   └── Summary: Unresolved
    ├── <MetadataSection />            ← business, poBox, residential flags
    └── <UspsSection />                ← USPS DPV confirmation & footnotes
        └── renderDpv()                ← DPV confirmation tag (Y/N/S/D)

Most of the frontend code uses nullish coalescing operators (??), a modern JavaScript feature introduced in ES2020, to safely display values - even when those values might be null or undefined. For example, the line below:

<dd>{postalAddress.regionCode ?? '—'}</dd>

Shows regionCode or fallbacks to '—' if it's missing. This provides a safe and predictable way to provide fallback values only when the left hand expression is null or undefined - and not when it's a falsy value like 0, '', or false.

Deploy and run

To test the app, go to https://google-address-validation.afi.dev/ and enter an address into the text box on the top left. Choose a country, and click [Validate].

If you'd like to run the app locally, fork google_address_validation on GitHub and run npm install to install dependencies. Start the app by running npm run dev, then open http://localhost:3000 in your browser to use it. Remember to update the .env files in the backend with a Google Maps API key that has the Address Validation API key enabled.

Coming up

In the final section of this series, I’ll show you how to use the Building Outlines and Entrances API to help users locate addresses and find entry points more easily on a map.

👋 As always, if you have any questions or suggestions for me, please reach out or say hello on LinkedIn.

Next: Part 5: Google Maps Building Outlines and Entrances API