Implement Custom Tasks in Your Skill

If you have an Alexa skill, you can modify it to perform tasks. This walkthrough shows how to create a custom task in your Alexa skill. You can use custom tasks to make functionality available to other skills via direct skill connections. For more information about skill connections, see Understand Skill Connections.

Prerequisites

This walkthrough requires the use of the ASK CLI v2, which can be found in the ASK CLI GitHub repo.

Modify your skill package to support custom tasks

When you create or modify a skill with the ASK CLI, rather than the developer console, you can edit your skill package directly. For more information, see Skill package format.

To modify your skill package

  • In the skill-package folder where you store your skill.json file, create a subfolder named tasks. The tasks folder is for task definition files.
Skill package folder layout with tasks
Skill package folder layout with tasks

Create a task definition file

Task definitions follow the OpenAPI V3 specifications, with the following restrictions:

  1. The name of the task definition file must be in the form {TaskName}.{TaskVersion}.json, where {TaskName} is the task name and {TaskVersion} is the task version number. For example, CountDown.1.json.
  2. The name of the task is represented as a path. For example, if your task name is CountDown, in OpenAPI spec, it would be /CountDown.
  3. The version of the task can only be a positive integer.
  4. Only one task definition is allowed per file, which means there should only be one path per file.
  5. Only inline references are currently allowed.
  6. The payload of the task is represented as the POST body of the previously mentioned specified path, and POST is the only HTTP method that is supported.
  7. The requestBody of the POST supports only application/json.
  8. The responses of the POST supports only application/json.
  9. The x-amzn-alexa-access-scope field of the info is a custom extension field for access control that supports public and vendor-private settings. If no access scope value is specified in the task definition, the default value is vendor-private.
  10. The x-amzn-display-details field of the info object is a custom extension field to add language localization mapping for the title field of the skill. If no x-amzn-display-details value is specified, the default value of the title field is used.
  11. The x-amzn-display-details field inside the input properties of the task component is a custom extension field to add human-readable language localization mapping for the task input. If no display details value is specified, the name of the property itself inside properties is used.
  12. The task must have at least one input parameter.

The following ASK CLI 2.0 example is a response.

$ ask smapi get-task -s <yourSkillId> --task-name amzn1.ask.skill.12345678-90ab-cdef-1234-567890abcdef.CountDown --task-version 1
  {
   "openapi": "3.0.0",
   "info": {
     "title": "A task to count down numbers",
     "version": "1",
     "x-amzn-alexa-access-scope": "public",
     "x-amzn-display-details": {
      "en-US": {
       "title": "A task to count down numbers"
      },
      "es-US": {
       "title": "Una tarea para contar números"
      }
     }
   },
   "tags": [{
     "name": "count down"
   }],
   "paths": {
     "/CountDown": {
       "summary": "Count Down",
       "description": "To start a count down",
       "post": {
         "requestBody": {
           "content": {
             "application/json": {
               "schema": {
                 "$ref": "#/components/schemas/Input"
               }
             }
           }
         },
         "responses": {
           "200": {
             "description": "When the count down finishes successfully",
             "content": {
               "application/json": {
                 "schema": {
                   "$ref": "#/components/schemas/SuccessfulResponse"
                 }
               }
             }
           },
           "400": {
             "description": "When the given parameters fail validations - e.g. lowerLimit cannot be higher than upperLimit"
           },
           "500": {
             "description": "When the count down fails"
           }
         }
       }
     }
   },
   "components": {
     "schemas": {
       "Input": {
         "type": "object",
         "properties": {
           "upperLimit": {
             "type": "number",
             "maximum": 100,
             "minimum": 1,
             "x-amzn-display-details": {
              "en-US": {
               "name": "Upper Limit"
              },
              "es-US": {
               "name": "Limite superior"
              }
             }
           },
           "lowerLimit": {
             "type": "number",
             "maximum": 100,
             "minimum": 1,
             "x-amzn-display-details": {
              "en-US": {
               "name": "Lower Limit"
              },
              "es-US": {
               "name": "Límite inferior"
              }
             }
           }
         }
       },
       "SuccessfulResponse": {
         "type": "object",
         "properties": {
           "endTime": {
             "type": "string",
             "format": "date-time"
           }
         }
       }
     }
   }
}

To create a task definition file

  • In a text editor, create a new task definition file in /tasks directory called CountDown.1.json, and then copy the following JSON into it.
{
   "openapi": "3.0.0",
   "info": {
     "title": "Task to perform a count down",
     "version": "1",
     "x-amzn-alexa-access-scope": "public"
   },
   "tags": [{
     "name": "count down"
   }],
   "paths": {
     "/CountDown": {
       "summary": "Count Down",
       "description": "To start a count down",
       "post": {
         "requestBody": {
           "content": {
             "application/json": {
               "schema": {
                 "$ref": "#/components/schemas/Input"
               }
             }
           }
         },
         "responses": {
           "200": {
             "description": "When the count down finishes successfully",
             "content": {
               "application/json": {
                 "schema": {
                   "$ref": "#/components/schemas/SuccessfulResponse"
                 }
               }
             }
           },
           "400": {
             "description": "When the given parameters fail validations - e.g. lowerLimit cannot be higher than upperLimit"
           },
           "500": {
             "description": "When the count down fails"
           }
         }
       }
     }
   },
   "components": {
     "schemas": {
       "Input": {
         "type": "object",
         "properties": {
           "upperLimit": {
             "type": "number",
             "maximum": 100,
             "minimum": 1
           },
           "lowerLimit": {
             "type": "number",
             "maximum": 100,
             "minimum": 1
           }
         }
       },
       "SuccessfulResponse": {
         "type": "object",
         "properties": {
           "endTime": {
             "type": "string",
             "format": "date-time"
           }
         }
       }
     }
   }
}

Add test examples to certify your task definitions

When you test and submit your skill for certification the functionality of your custom task will be verified. You must provide test examples for your custom task in the task definition file. These test examples are used to generate input to invoke the custom task handler under your skill's AWS Lambda function during pre-certification and certification validation.

Test example definitions follow the OpenAPI V3 specification, with the following restrictions:

  1. For each custom task, there must be at least one test example provided along with the custom task definition. You can provide up to five test examples for each custom task.
  2. Make sure that your test examples are valid and result in a successful response when your skill's custom task is invoked.
  3. Each test example includes the following fields:
Name Required? Type Description
summary No String Summary of the test example.
description No String Description of the test example.
value Yes Object Valid custom task input that conforms to the task definition.

To add test examples to a task definition file

  • In a text editor, add test examples to your task definition file under this path: post/requestBody/content/application/json/examples. Following is an example of a task definition request body with test examples added.
"requestBody": {
    "content": {
        "application/json": {
            "schema": {
                "$ref": "#/components/schemas/Input"
            },
            "examples": {
                "countdownTenToOne": {
                    "summary": "Example input for countdown from 10 to 1.",
                    "description": "The example inputs are used for validation of the task",
                    "value": {
                        "upperLimit": 10,
                        "lowerLimit": 1
                    }
                },
                "countdownHundredToNinety": {
                    "summary": "Example input for countdown from 100 to 90",
                    "description": "The example inputs are used for validation of the task",
                    "value": {
                        "upperLimit": 100,
                        "lowerLimit": 90
                    }
                }
            }
        }
    }
}

Implement task functionality in an AWS Lambda function

Next, implement the CountDown functionality in AWS Lambda. This walkthrough uses Python.

class CountDownTaskHandler(AbstractRequestHandler):
  def can_handle(self, handler_input):
    return is_request_type("LaunchRequest")(handler_input)\
      and handler_input.request_envelope.request.task.name == "amzn1.ask.skill.12345678-90ab-cdef-1234-567890abcdef.CountDown"
      
  def handle(self, handler_input):
    # Perform countdown and complete the task
    response_builder = handler_input.response_builder
    
    task = handler_input.request_envelope.request.task

    lower_limit = task.input['lowerLimit']
    upper_limit = task.input['upperLimit']

    count_down_speech = [i + '<break time="1ms" />' for i in range(lower_limit, upper_limit)]
    response_builder.speak("<ssml>" + "".join(count_down_speech) + "</ssml>")
    response_builder.add_directive(CompleteTaskDirective(...))

    return response_builder.response

Make your task available to other skills

To make your new task available to other skills, you must register the skill that implements the same custom task created in your skill package.

To register the skill that implements the task

  • Edit your skill.json file as shown in the following example.
{
  ...
  "apis": {
    "custom": {
      ...
      "tasks": [{
        "name": "CountDown",
        "version": "1"
      }]
    }
  }
}

Deploy your skill

To make your skill's tasks available to requester skills, you must deploy the skill as follows.

To deploy a new skill

  • In the ASK CLI, run the ask deploy command.

To add custom tasks to an existing skill for deployment

After you deploy your skill, the task is created under your skill's private namespace in the development stage.

To invoke your task handler

  • Invoke your task handler through the ASK CLI invoke-skill-end-point command as shown in the following example to confirm that your task logic executes correctly. Note that this command doesn't execute the task workflow; to do that, your skill must be certified and published. For more information about how to use this command, see the ASK CLI Command Reference.
$ ask smapi invoke-skill-end-point -s {skillId} --stage {skillStage} --skill-request-body file:./input.json --endpoint-region {endpoint-region}

The input.json file looks like the following example.

{
    "version": "1.0",
    "session": {
        "new": true,
        "sessionId": "amzn1.echo-api.session.aaf7b112-434c-11e7-2563-6bbd1672c748",
        "application": {
            "applicationId": "{YourProviderSkillId}"
        },
        "user": {
            "userId": "amzn1.ask.account.12345ABCDEFGH"
    	}
    },
    "context": {
        "System": {
            "application": {
                "applicationId": "{YourProviderSkillId}"
            },
            "user": {
                "userId": "amzn1.ask.account.12345ABCDEFGH"
            }
        }
    },
    "request": {
        "type": "LaunchRequest",
        "requestId": "amzn1.echo-api.request.da528275-5aa6-4f69-8038-06efc94d1923",
        "timestamp": "2020-01-29T23:11:48Z",
        "locale": "en-US",
        "task": {
            "name": "{YourProviderSkillId}.{taskName}",
            "version": "{taskVersion}",
            "input": {
                ...          
            }
        }
    }
}

Restrict access to your custom task

You can restrict access to a custom task. Two access levels are currently supported.

  • Public: Your task will be exposed to all skills.
  • Vendor Private: Your task will only be exposed to the skills under your vendor - i.e., it will not show up in search results performed by other vendors' skill developers.

By default, all custom tasks start with the Vendor Private access level. In order to set the access level for your task, you must modify the info section in your task definition file to include the x-amzn-alexa-access-scope field, with either public (for Public access) or vendor-private (for Vendor Private access).

How to make a custom task available for skill connections

To expose tasks to other skills, your provider skill must be certified and published. Submit your skill for certification and publication as described in Skill Certification and Publication. After it is certified and published to the live stage, your task becomes part of the task catalog and is available for other skills to use through Skill Connections.

A developer can discover your task via the ASK CLI search-task and get-task commands.

You can use the -h option to see command-line help for CLI commands, for example, ask smapi search-task -h.

The following ASK CLI 2.0 example shows the ask smapi search-task command with all options specified. (Only skillId is required. You can use any skillId under your developer account. provider-skill-id is optional. If the provider skill id is specified, the result will show all the custom tasks your developer account has access to under that provider skill.)

ask smapi search-task -s <skillId> --keywords <keywords> --provider-skill-id <providerSkillId> --max-results <max-results> —-next-token <nextToken>

The following ASK CLI 2.0 example shows the output.

$ ask smapi search-task -s {skillId} --keywords "count down"

{
  "taskSummaryList": [
    {
      "description": "A task to count down numbers",
      "name": "amzn1.ask.skill.12345678-90ab-cdef-1234-567890abcdef.CountDown",
      "version": "1"
    },
    {
      "description": "Count down numbers from 100 to 1",
      name": "amzn1.ask.skill.12345678-abcd-cdef-1234-abcdefabcdef.CountDown",
      "version": "1"
    }
  ]
}

$ ask smapi search-task -s <yourSkillId> --provider-skill-id "amzn1.ask.skill.12345678-abcd-cdef-1234-abcdefabcdef"
    
    {
      "taskSummaryList": [
        {
          "description": "Count down numbers from 100 to 1",
          "name": "amzn1.ask.skill.12345678-abcd-cdef-1234-abcdefabcdef.CountDown",
          "version": "1"
        }
      ]
    }
   

The following ASK CLI 2.0 example shows the ask smapi get-task command with all options specified (All the options are required.)

ask smapi get-task -s <yourSkillId> --task-name <providerSkillID>.<name> --task-version <task-version>

The following ASK CLI 2.0 example is a response.

Here is the command:

$ ask smapi get-task -s <yourSkillId> --task-name amzn1.ask.skill.12345678-90ab-cdef-1234-567890abcdef.CountDown --task-version 1

Here is the output:

{
   "openapi": "3.0.0",
   "info": {
     "title": "A task to count down numbers",
     "version": "1",
     "x-amzn-alexa-access-scope": "public"
   },
   "tags": [{
     "name": "count down"
   }],
   "paths": {
     "/CountDown": {
       "summary": "Count Down",
       "description": "To start a count down",
       "post": {
         "requestBody": {
           "content": {
             "application/json": {
               "schema": {
                 "$ref": "#/components/schemas/Input"
               }
             }
           }
         },
         "responses": {
           "200": {
             "description": "When the count down finishes successfully",
             "content": {
               "application/json": {
                 "schema": {
                   "$ref": "#/components/schemas/SuccessfulResponse"
                 }
               }
             }
           },
           "400": {
             "description": "When the given parameters fail validations - e.g. lowerLimit cannot be higher than upperLimit"
           },
           "500": {
             "description": "When the count down fails"
           }
         }
       }
     }
   },
   "components": {
     "schemas": {
       "Input": {
         "type": "object",
         "properties": {
           "upperLimit": {
             "type": "number",
             "maximum": 100,
             "minimum": 1
           },
           "lowerLimit": {
             "type": "number",
             "maximum": 100,
             "minimum": 1
           }
         }
       },
       "SuccessfulResponse": {
         "type": "object",
         "properties": {
           "endTime": {
             "type": "string",
             "format": "date-time"
           }
         }
       }
     }
   }
}

How does my task get invoked through Skill Connections?

After your task is certified and published to the live stage, your task can be requested by other skills through the task's canonical ID, which is a concatenation of your skillId and the task's name, such as {ProviderSkillId}.CountDown.

For a requester skill to connect to your task, that skill must return the following directive in its skill response. To request a custom task, requester skill must send a direct skill connection, specifying the provider skill ID in the URI.

{
  "type": "Connections.StartConnection",
  "uri": "connection://{ProviderSkillId}.CountDown/1?provider={ProviderSkillId}",
  "input": {
    "lowerLimit": 1,
    "upperLimit": 10
  }
}

For more details about how a task is requested, see Request a task from a provider skill.