How to Make an API Request from Your Alexa Skill Using Python

Franklin Lobb Jun 12, 2019
Share:
Python Tips & Tools Build
Blog_Header_Post_Img

As we design and create Alexa skills with increasing complexity and capabilities, the need arises to have skills interact with remote APIs and web servers. Many services we consume are accessible via an API, such as weather forecasts, geolocation services, Twitter feeds, and so on. At some point, every developer will run into a situation where they’ll need to access data by making HTTP requests to an API. HTTP requests allow you to fetch data from a remote source. It could be an API, a website, or something else.

Making HTTP requests is a core functionality for modern languages, and when it comes to Python there are a fair amount of solutions to this problem, both built into the language and by the developer community. In this example skill, we look at one of the most popular methods you can use to call an external API through your Alexa skill using the “Requests” library in Python.

Understanding the Voice Interaction

To make it simple, we’re going to use a fact skill as our base. In this example, our customers will interact with the skill by asking for a fact about a number. Let’s take a look at a simple interaction between our skill and our customer:

Customer: Alexa, ask number facts to give me a fact about seven.

Alexa: Seven is the number of main stars in the constellations of the Big Dipper and Orion.

When our customer says, “give me a fact about seven” the GetNumberFactIntent is triggered and the number slot is set to ‘7’. Our skill code makes an http get request to numbersapi.com to get a random fact about the number ‘7’. You can test out the API by typing: http://numbersapi.com/7 into your browser.

Understanding the Hard-Coded Facts

When we first built our number facts skill, we hardcoded our facts straight into the code. Let’s take a moment to understand how the code works before we update our code to get the facts from numbersapi.com.

First, we defined an object called number_facts. It functions as a lookup table. Take a moment to look through the table:

Copied to clipboard
number_facts = {
    "1": "is the number of moons orbiting the earth.",
    "2": "is the number of stars in a binary star system (a stellar system consisting of two stars orbiting around their center of mass).",
    "3": "is the number of consecutive successful attempts in a hat trick in sports.",
    "4": "is the number of movements in a symphony.",
    "5": "is the number of basic tastes (sweet, salty, sour, bitter, and umami).",
    "6": "is the number of fundamental flight instruments lumped together on a cockpit display.",
    "7": "is the number of main stars in the constellations of the Big Dipper and Orion.",
    "8": "is the number of bits in a byte.",
    "9": "is the number of innings in a regulation, non-tied game of baseball.",
    "10": "is the number of hydrogen atoms in butane, a hydrocarbon.",
    "11": "is the number of players in a football team."
}

If our customer asked for a fact about ‘7’, we can pass it to number_facts to get the fact. The code to do that would look like:

Copied to clipboard
number_facts["7"]

Since we don’t want to hardcode the number 7, let’s take a look at how we’d look up the fact for our number slot.

Copied to clipboard
the_number = handlerInput.requestEnvelope.request.intent.slots.number.value
the_fact = number_facts[the_number]

Below is the sample code for GetNumberFactHandler before we make any updates:

Copied to clipboard
class GetNumberFactHandler(AbstractRequestHandler):
    """Handler for get number fact intent."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return (is_request_type("LaunchRequest")(handler_input) or 
            is_intent_name("GetNumberFactIntent")(handler_input))
    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        logger.info("In GetNumberFactHandler")
        # to enable randint, add "from random import randint" at start of file
        the_number = None
        if is_request_type("IntentRequest")(handler_input):
            the_number = handler_input.request_envelope.request.intent.slots["number"].value
        if the_number is None:
            the_number = str(randint(1,11))
        the_fact = the_number + " " + number_facts[the_number]
        speech = the_number + " " + the_fact + " Would you like to hear another fact?"
        handler_input.response_builder.speak(speech).ask(speech)
        return handler_input.response_builder.response

We simply access the number slot and look up the fact using the number_facts object and then have our skill tell the user the fact by returning a response using the response_builder.

Using the “Requests” Library

Now that we understand the basics of this fact skill, let’s look at how to access information outside of our skill, by using the requests library to access facts from numbersapi.com

The requests library is the de facto standard for making HTTP requests in Python. It abstracts the complexities of making requests behind a beautiful, simple API so that you can focus on utilizing data and interacting with services in your skill.

Let’s see how we’ve modified the code to use the “requests” library to call our API.

Step 1: Included the requests library in our skill using the import statement.

Copied to clipboard
   import requests

Step 2: Call the requests.get method from within your intent(GetNumberFactIntent) to send a query (number requested by the user) to the API, and respond back with the result.

Copied to clipboard
class GetNumberFactHandler(AbstractRequestHandler):
    """Handler for get number fact intent."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return (is_request_type("LaunchRequest")(handler_input) or 
            is_intent_name("GetNumberFactIntent")(handler_input))
    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        logger.info("In GetNumberFactHandler")
        the_number = None
        if is_request_type("IntentRequest")(handler_input):
            the_number = handler_input.request_envelope.request.intent.slots["number"].value
        if the_number is None:
            the_number = str(randint(1,11))
        url = "http://numbersapi.com/" + the_number
        response = requests.get(url)
        if response.status_code == 200:
            the_fact = response.text
        else:
            the_fact = "I had trouble getting a fact about " + the_number + ".";
        speech = the_fact + " Would you like to hear another fact?"
        handler_input.response_builder.speak(speech).ask(speech)
        return handler_input.response_builder.response

As you can see, the “requests” module makes it super simple for us to call our API. All we had to do was specify the URL for the API to be called, and the response is made available to us inside the text property, which we can then use as part of our response. If the response is JSON, you can use the json property instead.

Whenever calling an API you will want to include error checking. I chose to check the response code to ensure it was a success code (200). You might choose to use a try-except block.

If you didn’t edit your skill code in the Lambda console, the next step would be to zip up your lambda_function.py file along with other modules to create a deployment package and upload it to your Lambda function. To learn more about how to do that, read AWS Lambda Deployment Package in Python.

More Resources