Creating An Alexa Skill That Calls An API

Introduction

 
Here is another article on custom Alexa skills. In this article, I am going to demonstrate how to create a custom Alexa skill on an Alexa Hosted node.js server that can actually call an API. For that, I will be creating a custom Alexa skill named space patrol. The Alexa skill will be making requests to answer questions such as how many astronauts are there in space right now. With the help of this skill, once a user invokes Alexa, Alexa will fetch information from the external API and in response tell the user about the number of astronauts in space.
 
The backend code required for the Alexa skill to work will be stored in a lambda function inside the Amazon developer console.
 

Creating an Alexa Skill

 
To create a new skill, first, we need to login into the Alexa developer console, we need to mention the unique skill name and select the default language according to our location.
 
 
After that, we can choose a model to add to our skill. To create custom skills, we can select the custom model.
 
 
We can also choose a method or a template to host the skill’s backend code inside a lambda function.
 
 
We can choose Alexa hosted node.js or python template. We can also mention our own endpoint or server to store backend resources for the required Alexa skills. The next step is to choose a template to add to our skill, which we customize later according to our need and click on the create skill button.
 
Now as a skill has been created, we need to make adjustments to the skill’s frontend. Now I will be creating intents and sample utterances to create skill’s frontend.
 
First, we need to mention the invocation name. Users say a skill's invocation name to begin an interaction with a particular custom skill.
 
 
Now we have to create intents:
 
 
Here, I have added a new intent named GetRemoteDataIntent along with some sample utterances such as how many people are in space, how many people are in space now, and how many people are currently in space. No slots and custom slot types are defined for this Alexa skill.
 
After creating a model for a particular skill, we can save and build the model by clicking on the save model and build the model button on the top.
 
JSON code for the above frontend is as follows:
  1. {  
  2.     "interactionModel": {  
  3.         "languageModel": {  
  4.             "invocationName""space petrol",  
  5.             "intents": [  
  6.                 {  
  7.                     "name""AMAZON.NavigateHomeIntent",  
  8.                     "samples": []  
  9.                 },  
  10.                 {  
  11.                     "name""AMAZON.CancelIntent",  
  12.                     "samples": []  
  13.                 },  
  14.                 {  
  15.                     "name""AMAZON.HelpIntent",  
  16.                     "samples": []  
  17.                 },  
  18.                 {  
  19.                     "name""AMAZON.StopIntent",  
  20.                     "samples": []  
  21.                 },  
  22.                 {  
  23.                     "name""GetRemoteDataIntent",  
  24.                     "slots": [],  
  25.                     "samples": [  
  26.                         "how many people are in space",  
  27.                         "how many people are in space now",  
  28.                         "how many people are currently in space",  
  29.                         "how many humans are in space",  
  30.                         "how many humans are in space now",  
  31.                         "how many humans are currently in space",  
  32.                         "how many astronauts are in space",  
  33.                         "how many astronauts are in space now",  
  34.                         "how many astronauts are currently in space"  
  35.                     ]  
  36.                 }  
  37.             ],  
  38.             "types": []  
  39.         }  
  40.     }  
  41. }  

Creating the backend resource for the Alexa skill

 
To create backend code inside a lambda function, we can write code inside the index.js node.js file. The code for the custom Alexa skill is as follows:
  1. const Alexa = require('ask-sdk-core');  
  2.   
  3. const GetRemoteDataHandler = {  
  4.   canHandle(handlerInput) {  
  5.     return handlerInput.requestEnvelope.request.type === 'LaunchRequest'  
  6.       || (handlerInput.requestEnvelope.request.type === 'IntentRequest'  
  7.       && handlerInput.requestEnvelope.request.intent.name === 'GetRemoteDataIntent');  
  8.   },  
  9.   async handle(handlerInput) {  
  10.     let outputSpeech = 'This is the default message.';  
  11.   
  12.     await getRemoteData('http://api.open-notify.org/astros.json')  
  13.       .then((response) => {  
  14.         const data = JSON.parse(response);  
  15.         outputSpeech = `There are currently ${data.people.length} astronauts in space. `;  
  16.         for (let i = 0; i < data.people.length; i += 1) {  
  17.           if (i === 0) {  
  18.               
  19.             outputSpeech = `${outputSpeech}Their names are: ${data.people[i].name}, `;  
  20.           } else if (i === data.people.length - 1) {  
  21.               
  22.             outputSpeech = `${outputSpeech}and ${data.people[i].name}.`;  
  23.           } else {  
  24.               
  25.             outputSpeech = `${outputSpeech + data.people[i].name}, `;  
  26.           }  
  27.         }  
  28.       })  
  29.       .catch((err) => {  
  30.         console.log(`ERROR: ${err.message}`);  
  31.           
  32.       });  
  33.   
  34.     return handlerInput.responseBuilder  
  35.       .speak(outputSpeech)  
  36.       .getResponse();  
  37.   },  
  38. };  
  39.   
  40. const HelpIntentHandler = {  
  41.   canHandle(handlerInput) {  
  42.     return handlerInput.requestEnvelope.request.type === 'IntentRequest'  
  43.       && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent';  
  44.   },  
  45.   handle(handlerInput) {  
  46.     const speechText = 'You can introduce yourself by telling me your name';  
  47.   
  48.     return handlerInput.responseBuilder  
  49.       .speak(speechText)  
  50.       .reprompt(speechText)  
  51.       .getResponse();  
  52.   },  
  53. };  
  54.   
  55. const CancelAndStopIntentHandler = {  
  56.   canHandle(handlerInput) {  
  57.     return handlerInput.requestEnvelope.request.type === 'IntentRequest'  
  58.       && (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent'  
  59.         || handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent');  
  60.   },  
  61.   handle(handlerInput) {  
  62.     const speechText = 'Goodbye!';  
  63.   
  64.     return handlerInput.responseBuilder  
  65.       .speak(speechText)  
  66.       .getResponse();  
  67.   },  
  68. };  
  69.   
  70. const SessionEndedRequestHandler = {  
  71.   canHandle(handlerInput) {  
  72.     return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';  
  73.   },  
  74.   handle(handlerInput) {  
  75.     console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`);  
  76.   
  77.     return handlerInput.responseBuilder.getResponse();  
  78.   },  
  79. };  
  80.   
  81. const ErrorHandler = {  
  82.   canHandle() {  
  83.     return true;  
  84.   },  
  85.   handle(handlerInput, error) {  
  86.     console.log(`Error handled: ${error.message}`);  
  87.   
  88.     return handlerInput.responseBuilder  
  89.       .speak('Sorry, I can\'t understand the command. Please say again.')  
  90.       .reprompt('Sorry, I can\'t understand the command. Please say again.')  
  91.       .getResponse();  
  92.   },  
  93. };  
  94.   
  95. const getRemoteData = (url) => new Promise((resolve, reject) => {  
  96.   const client = url.startsWith('https') ? require('https') : require('http');  
  97.   const request = client.get(url, (response) => {  
  98.     if (response.statusCode < 200 || response.statusCode > 299) {  
  99.       reject(new Error(`Failed with status code: ${response.statusCode}`));  
  100.     }  
  101.     const body = [];  
  102.     response.on('data', (chunk) => body.push(chunk));  
  103.     response.on('end', () => resolve(body.join('')));  
  104.   });  
  105.   request.on('error', (err) => reject(err));  
  106. });  
  107.   
  108. const skillBuilder = Alexa.SkillBuilders.custom();  
  109.   
  110. exports.handler = skillBuilder  
  111.   .addRequestHandlers(  
  112.     GetRemoteDataHandler,  
  113.     HelpIntentHandler,  
  114.     CancelAndStopIntentHandler,  
  115.     SessionEndedRequestHandler,  
  116.   )  
  117.   .addErrorHandlers(ErrorHandler)  
  118.   .lambda();  
To receive request from the user, request handlers are created for each intent to handle. Inside each handlers, canHandle and handle functions are defined.
 
The canHandle() function is where you define what requests the handler responds to. The handle() function returns a response to the user. If your skill receives a request, the canHandle() function within each handler determines whether or not that handler can service the request.
 
In this case, GetRemoteDataHandler has been created. The canHandle() function inside GetRemoteDataHandler contains LaunchRequest as well as IntentRequest. The user can launch or invoke Alexa by saying open followed by the Invocation name which in this case is space petrol, which is a LaunchRequest. Alexa can also be invoked by saying “Alexa ask followed by invocation name and a sample utterance”. Therefore, the canHandle() function within the GetRemoteDataHandler will let the SDK know it can fulfill the request. In computer terms, the canHandle returns true to confirm it can do the work.
 
With the help of the handle() function inside GetRemoteDataHandler, Alexa will be making requests to answer questions such as "How many astronauts are there in space right now?"
 
Inside the skill, a function named getRemoteData is defined which is making the call to the API, and then it returns all the information. The information is returned inside the response variable as an argument of getRemoteData function inside GetRemoteDataHandler. There is a for loop inside the function, looping through each of those records that were returned. Alexa will be speaking back the names of each astronaut.
 
Output
 
 
As we can see from the output the skill has been invoked by saying “Alexa ask space petrol how many people are in space”. Alexa then calls an external API where all the information regarding the number of astronauts is available, fetches all the information, and speaks astronaut names in her response to the user.
 

Summary

 
In this article, I created a custom Alexa skill that calls an external API. I also defined the invocation name, intents, and sample utterances. I created a function that calls an external API and then fetches all the information needed to send back as a response to the user each time a user asks for it. Proper coding snippets along with the output for the backend of skill is also provided.