Code-based Actions: Capture and validate an Address

You can extract and validate addresses using the Google Maps Geocoding API. Address information can consist of the street, house number, postal code, PO box, city, state, country, etc. Using Flow and the Google Maps Geocoding API, you can capture all these information insteading asking the user to provide the entire address.

Design your flow and capture the address

Bots can designed to extract house numbers and cities using entities through extensive data training. Adding a hundred annotated examples might not give you a 100% result, it is easier to use an Any Text slot of the entity type Text.

Using the Any Text trigger you can capture any user input and use that within a code action to geo-code it to structured data.

Validating the address with the Google API

There are different providers, but for this example we are using the Google Maps Geocoding API to convert user input into structured address information. To use the Geocoding API you need to get an API key. The API key is a unique identifier used to authenticate requests with Google for usage and billing purposes.

Writing the code action

The first step is getting the address information. Any text works the same as any other params.

With the code example below, we will receive the address and output it to the logging console:

async payload => {  
  const {  
    params,  
    user  
  } = payload

  const { address } = params

  const lastAddress = address[address.length - 1].value

  console.info('The last address captured', lastAddress)  
}  


Validate and transform it into structured address data using the Google API:

async payload => {  
  try {  
    const {  
      params,  
      user  
    } = payload  
const { address } = params

const lastAddress = address[address.length - 1].value

// For this example we show the API key in code  
// but best practice is to use Configuration  
// <https://flow.ai/docs/actions/code_configuration>  
const key='MY SECRET API KEY'

// Construct a Google API URL  
const url=encodeURI(`https://maps.googleapis.com/maps/api/geocode/json?address=${lastAddress}&key=${key}&language=${user.profile.locale || 'en'}`)

// Call the API  
const { data } = await request(url)  
const { status, results } = data

console.info('The API converted the address to', results)

  } catch(err) {  
    console.error('Failed to call the API', err)  
  }  
}

Below is a sample geocoding result, in JSON:

\[  
  {  
      "address_components" : \[  
        {  
            "long_name" : "1600",  
            "short_name" : "1600",  
            "types" : [ "street_number" ]  
        },  
        {  
            "long_name" : "Amphitheatre Pkwy",  
            "short_name" : "Amphitheatre Pkwy",  
            "types" : [ "route" ]  
        },  
        {  
            "long_name" : "Mountain View",  
            "short_name" : "Mountain View",  
            "types" : [ "locality", "political" ]  
        },  
        {  
            "long_name" : "Santa Clara County",  
            "short_name" : "Santa Clara County",  
            "types" : [ "administrative_area_level_2", "political" ]  
        },  
        {  
            "long_name" : "California",  
            "short_name" : "CA",  
            "types" : [ "administrative_area_level_1", "political" ]  
        },  
        {  
            "long_name" : "United States",  
            "short_name" : "US",  
            "types" : [ "country", "political" ]  
        },  
        {  
            "long_name" : "94043",  
            "short_name" : "94043",  
            "types" : [ "postal_code" ]  
        }  
      ],  
      "formatted_address" : "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA",  
      "geometry" : {  
        "location" : {  
            "lat" : 37.4224764,  
            "lng" : -122.0842499  
        },  
        "location_type" : "ROOFTOP",  
        "viewport" : {  
            "northeast" : {  
              "lat" : 37.4238253802915,  
              "lng" : -122.0829009197085  
            },  
            "southwest" : {  
              "lat" : 37.4211274197085,  
              "lng" : -122.0855988802915  
            }  
        }  
      },  
      "place_id" : "ChIJ2eUgeAK6j4ARbn5u_wAGqWA",  
      "types" : [ "street_address" ]  
  }  
]

Read the Google documentation for the expected response details.

Covering any edge cases

The Google API might return multiple results, confirm the returned address with the user.

Flow handles edge cases (if any):

  • If the Google API returns an error
  • If the API did not give any valid results
  • If the API returns us multiple addresses

Flow design example:

Within the code action, you can send a Carousel component to display the results, and ask the user to verify the address:

async payload => {

  try {  
    const {  
      params,  
      user  
    } = payload
const { address } = params

const lastAddress = address[address.length - 1].value

// Add the API key to the configuration
const key = await toolbelt.config.get('api_key')

// Build url
const url=encodeURI(`https://maps.googleapis.com/maps/api/geocode/json?address=${lastAddress}&key=${key}&language=${user.profile.locale || 'en'}`)

const { data } = await request(url)
const { status, results } = data
if(status !== 'OK') {
  return trigger('INVALID_ADDRESS')
}

/*
 * Simple helper function to find a piece
 * of address information within the API results
 */
const findAddressPart = (result, part) => {
  const component = result.address_components.find(c => c.types.indexOf(part) !== -1)
  if(component) {
    return component.short_name
  }

  return ""
}

// Create a carousel
const carousel = new Carousel()

for (let i = 0; i < results.length; i++) {
  const result = results[i];

  // Create a card
  const card = new Card({
    title: result.formatted_address
  })

  const params = [
    new Param('delivery_street', findAddressPart(result, 'route')),
    new Param('delivery_zipcode', findAddressPart(result, 'postal_code')),
    new Param('delivery_housenumber', findAddressPart(result, 'street_number')),
    new Param('delivery_city', findAddressPart(result, 'locality')),
    new Param('delivery_country', findAddressPart(result, 'country'))
  ]

  card.addButton(new Button({
    label: "Confirm",
    type: 'event',
    value: 'ADDRESS_CONFIRMED',
    param: params
  }))

  carousel.addCard(card)
}

carousel.addQuickReply(new QuickReply({
  type: 'event',
  label: 'Try again',
  value: 'DELIVERY_ADDRESS'
}))

return new Message('We do not support audio only').addResponse(carousel)
    } catch(err) {  
    console.error('Failed to call the API', err)  
    trigger('INVALID_ADDRESS')  
  }  
}

Test your flow using the TRY IT OUT window:

Read more