Amazon Developer

as

Settings
Sign out
Notifications
Alexa
Amazon Appstore
Ring
AWS
Documentation
Support
Contact Us
My Cases
Category SDK
MCP toolkit
Certify
Resources

Create a Category Add-on Manually with the CLI

This guide provides the steps to build an add-on with the Category SDK.

Prerequisites

Before you begin, make sure you have set up your development environment. For details, see Set Up Your Development Environment.

You choose the category for your add-on when you initialize your projet. You can select from one of the following categories:

  • Food Ordering
  • Home Services
  • Local Booking
  • Restaurant Reservation
  • Ticketing

This doc explains how to create the add-on manually, using the Alexa AI CLI. Alternatively. you can use an AI coding agent instead. For details, see Create a Category Add-on Using an AI Coding Agent.

You can implement the handlers manually or by using an AI coding agent to generate the code.

High-level steps for building a category add-on

You complete the following steps to build your add-on.

  1. Initialize a new project for your intended category. This provides you with the initial starter code. See Initialize your add-on project.
  2. Implement the Service Provider Interfaces (SPIs) to call your APIs. See Implement the SPI handlers.
  3. Configure the add-on manifest file with required metadata for your add-on. See Configure the add-on manifest.
  4. Configure account linking if needed. Account linking is required for operations that modify user data.
  5. Build and deploy the add-on for testing. See Deploy the add-on.
  6. For categories that require local search, such as restaurant reservation, define a test Point of Interest (POI) catalog and associate it with your add-on. See Prepare Point of Interest (POI) data
  7. Test your add-on. You both test locally to validate your SPI implementation, and also test in the web simulator and on an Alexa device. See Test your add-on.
  8. Submit the add-on for certification. See Certification

The following diagram shows how category add-ons work.

Category add-on flow

Initialize your add-on project

You use the Alexa AI CLI to create a new add-on project.

This step installs a category-specific starter package and its dependencies. In this release, you can choose from the following categories:

  • Food Ordering
  • Home Services
  • Local Booking
  • Restaurant Reservation
  • Ticketing

This release supports typescript for the programming language, and cdk for the backend.

Open a command prompt and navigate to the directory where you want to create the add-on. Run the alexa-ai new action command. Respond to the prompts in the command line wizard.

Copied to clipboard.

alexa-ai new action

The following example shows completing this wizard for restaurant reservation add-on.

The following example shows how to complete this wizard for the restaurant reservation add-on. Use your business name (such as Uber, OpenTable) for the add-on display name. Alternatively, you can use "AddOnTest1" or "AddOnTest2" if you want to create multiple add-ons.

AddOn display name: AddOnTest1
Locale: (en-US)
Choose a category to start with:
  1. Food Ordering
  2. Home Services
  3. Local Booking
  4. Restaurant Reservation
  5. Ticketing
Your choice: 1
Choose the programming language:
  1. typescript (default)
Your choice: 1
Choose a method to host your AddOn's backend:
  1. cdk (default)
Your choice: 1


Cloning action template...


ACTION AddOn project created at: /path/to/my-addon/addontest1/


Next steps:
  cd addontest1
  npm install (to install NMP libraries from downloaded Sample template)
  alexa-ai deploy

The new project has the following structure.

myrestaurantapp/
├── action/
│   ├── infrastructure/cdk/                     # AWS CDK stacks
│   └── restaurant-reservation/
│       ├── api-samples.rest
│       ├── scripts/bundle/                     # Build scripts
│       ├── src/
│       │   ├── apigateway.ts                   # API Gateway handler setup
│       │   ├── config_builder.ts               # SPI config builder (operations + endpoints)
│       │   ├── handlers/                       # Lambda entry points (one per operation)
│       │   │   ├── create_reservation_handler.ts
│       │   │   ├── delete_reservation_handler.ts
│       │   │   ├── get_availability_handler.ts
│       │   │   └── update_reservation_handler.ts
│       │   ├── local/                          # Local testing utilities
│       │   └── operations/                     # Business logic (what YOU implement)
│       │       ├── create_reservation.ts
│       │       ├── delete_reservation.ts
│       │       ├── get_availability.ts
│       │       └── update_reservation.ts
│       ├── test-suites/
│       │   └── test-config.json
│       └── tests/                              # Unit tests per operation
│           ├── api_gateway.test.ts
│           ├── create_reservation.test.ts
│           ├── delete_reservation.test.ts
│           ├── get_availability.test.ts
│           └── update_reservation.test.ts
├── addon-package/
│   ├── action/
│   │   └── RestaurantReservationService-config.json   # SPI config: operations, endpoints
│   └── addon.json                              # Manifest: store listing, permissions, integrations
├── cdk.json
├── jest.config.js
├── package.json
├── README.md
└── tsconfig.json

myfoodorderingapp/
├── action/
│   ├── food-ordering/
│   │   ├── scripts/bundle/
│   │   │   └── index.js
│   │   ├── src/
│   │   │   ├── apigateway.ts                   # API Gateway handler setup
│   │   │   ├── config_builder.ts               # SPI config builder (operations + endpoints)
│   │   │   ├── handlers/                       # Lambda entry points (one per operation)
│   │   │   │   ├── batch_get_menu_items_handler.ts
│   │   │   │   ├── cancel_purchase_handler.ts
│   │   │   │   ├── create_cart_item_handler.ts
│   │   │   │   ├── create_purchase_handler.ts
│   │   │   │   ├── delete_cart_item_handler.ts
│   │   │   │   ├── get_address_handler.ts
│   │   │   │   ├── get_addresses_handler.ts
│   │   │   │   ├── get_cart_handler.ts
│   │   │   │   ├── get_cart_item_handler.ts
│   │   │   │   ├── get_menu_category_handler.ts
│   │   │   │   ├── get_menu_item_handler.ts
│   │   │   │   ├── get_order_handler.ts
│   │   │   │   ├── get_orders_handler.ts
│   │   │   │   ├── get_payment_method_handler.ts
│   │   │   │   ├── get_payment_methods_handler.ts
│   │   │   │   ├── get_purchase_handler.ts
│   │   │   │   ├── get_purchases_handler.ts
│   │   │   │   ├── get_restaurant_availability_handler.ts
│   │   │   │   ├── get_restaurant_handler.ts
│   │   │   │   ├── preview_purchase_handler.ts
│   │   │   │   ├── recreate_purchase_handler.ts
│   │   │   │   ├── search_restaurants_handler.ts
│   │   │   │   ├── submit_purchase_handler.ts
│   │   │   │   ├── update_cart_item_handler.ts
│   │   │   │   └── update_purchase_handler.ts
│   │   │   ├── local/
│   │   │   │   └── server.ts                   # Local testing server
│   │   │   └── operations/                     # Business logic (what YOU implement)
│   │   │       ├── batch_get_menu_items.ts
│   │   │       ├── cancel_purchase.ts
│   │   │       ├── create_cart_item.ts
│   │   │       ├── create_purchase.ts
│   │   │       ├── delete_cart_item.ts
│   │   │       ├── get_address.ts
│   │   │       ├── get_addresses.ts
│   │   │       ├── get_cart.ts
│   │   │       ├── get_cart_item.ts
│   │   │       ├── get_menu_category.ts
│   │   │       ├── get_menu_item.ts
│   │   │       ├── get_order.ts
│   │   │       ├── get_orders.ts
│   │   │       ├── get_payment_method.ts
│   │   │       ├── get_payment_methods.ts
│   │   │       ├── get_purchase.ts
│   │   │       ├── get_purchases.ts
│   │   │       ├── get_restaurant.ts
│   │   │       ├── get_restaurant_availability.ts
│   │   │       ├── preview_purchase.ts
│   │   │       ├── recreate_purchase.ts
│   │   │       ├── search_restaurants.ts
│   │   │       ├── submit_purchase.ts
│   │   │       ├── update_cart_item.ts
│   │   │       └── update_purchase.ts
│   │   └── tests/
│   │       ├── create_purchase.test.ts
│   │       ├── get_cart_item.test.ts
│   │       ├── get_restaurant.test.ts
│   │       ├── search_restaurants.test.ts
│   │       └── submit_purchase.test.ts
│   └── infrastructure/cdk/
│       ├── bin/
│       │   └── app.ts
│       └── lib/
│           └── add-on-infra-stack.ts
├── addon-package/
│   ├── action/
│   │   └── FoodOrderingService-config.json     # SPI config: operations, endpoints
│   ├── addon.json                              # Manifest: store listing, permissions, integrations
│   └── test-suites/
│       └── test-config.json
├── cdk.json
├── jest.config.js
├── package.json
└── tsconfig.json

myhomeservicesapp/
├── action/
│   ├── home-services/
│   │   ├── api-samples.rest
│   │   ├── scripts/bundle/                   # Build scripts
│   │   ├── src/
│   │   │   ├── apigateway.ts               # API Gateway handler setup
│   │   │   ├── config_builder.ts           # SPI config builder (operations + endpoints)
│   │   │   ├── handlers/                    # Lambda entry points (one per operation)
│   │   │   │   ├── cancel_booking_handler.ts
│   │   │   │   ├── create_booking_handler.ts
│   │   │   │   ├── get_booking_handler.ts
│   │   │   │   ├── get_service_specification_handler.ts
│   │   │   │   ├── get_services_handler.ts
│   │   │   │   ├── get_user_info_handler.ts
│   │   │   │   ├── list_professionals_handler.ts
│   │   │   │   ├── message_professional_handler.ts
│   │   │   │   └── modify_booking_handler.ts
│   │   │   ├── local/                       # Local testing utilities
│   │   │   └── operations/                   # Business logic (what YOU implement)
│   │   │       ├── cancel_booking.ts
│   │   │       ├── create_booking.ts
│   │   │       ├── get_booking.ts
│   │   │       ├── get_service_specification.ts
│   │   │       ├── get_services.ts
│   │   │       ├── get_user_info.ts
│   │   │       ├── list_professionals.ts
│   │   │       ├── message_professional.ts
│   │   │       └── modify_booking.ts
│   │   ├── test-suites/
│   │   │   └── test-config.json
│   │   └── tests/                             # Unit tests per operation
│   │       ├── api_gateway.test.ts
│   │       ├── cancel_booking.test.ts
│   │       ├── create_booking.test.ts
│   │       ├── get_booking.test.ts
│   │       ├── get_service_specification.test.ts
│   │       ├── get_services.test.ts
│   │       ├── get_user_info.test.ts
│   │       ├── list_professionals.test.ts
│   │       ├── message_professional.test.ts
│   │       └── modify_booking.test.ts
│   └── infrastructure/cdk/                    # AWS CDK stacks
├── addon-package/
│   ├── action/
│   │   └── HomeServicesService-config.json     # SPI config: operations, endpoints
│   └── addon.json                               # Manifest: store listing, permissions, integrations
├── cdk.json
├── jest.config.js
├── package.json
├── README.md
└── tsconfig.json

mylocalbookingapp/
├── action/
│   ├── infrastructure/cdk/
│   │   ├── bin/
│   │   │   └── app.ts
│   │   └── lib/
│   │       └── add-on-infra-stack.ts
│   └── local-booking/
│       ├── api-samples.rest
│       ├── scripts/bundle/
│       │   └── index.js
│       ├── src/
│       │   ├── apigateway.ts                   # API Gateway handler setup
│       │   ├── config_builder.ts               # SPI config builder (operations + endpoints)
│       │   ├── handlers/                       # Lambda entry points (one per operation)
│       │   │   ├── cancel_appointment_handler.ts
│       │   │   ├── create_appointment_handler.ts
│       │   │   ├── get_appointment_handler.ts
│       │   │   ├── get_availability_handler.ts
│       │   │   ├── get_business_handler.ts
│       │   │   ├── get_reviews_handler.ts
│       │   │   ├── get_services_handler.ts
│       │   │   ├── get_user_info_handler.ts
│       │   │   └── update_appointment_handler.ts
│       │   ├── local/
│       │   │   └── server.ts                   # Local testing server
│       │   └── operations/                     # Business logic (what YOU implement)
│       │       ├── cancel_appointment.ts
│       │       ├── create_appointment.ts
│       │       ├── get_appointment.ts
│       │       ├── get_availability.ts
│       │       ├── get_business.ts
│       │       ├── get_reviews.ts
│       │       ├── get_services.ts
│       │       ├── get_user_info.ts
│       │       └── update_appointment.ts
│       ├── test-suites/
│       │   └── test-config.json
│       └── tests/
│           ├── cancel_appointment.test.ts
│           ├── create_appointment.test.ts
│           ├── get_appointment.test.ts
│           ├── get_availability.test.ts
│           ├── get_business.test.ts
│           ├── get_reviews.test.ts
│           ├── get_services.test.ts
│           ├── get_user_info.test.ts
│           └── update_appointment.test.ts
├── addon-package/
│   ├── action/
│   │   └── LocalBookingService-config.json     # SPI config: operations, endpoints
│   └── addon.json                              # Manifest: store listing, permissions, integrations
├── cdk.json
├── jest.config.js
├── package.json
├── poi-data/
│   └── test-poi-data.json
└── tsconfig.json

myticketingapp/
├── action/
│   ├── infrastructure/cdk/
│   │   ├── bin/
│   │   │   └── app.ts
│   │   └── lib/
│   │       └── add-on-infra-stack.ts
│   └── ticketing/
│       ├── scripts/bundle/
│       │   └── index.js
│       ├── src/
│       │   ├── apigateway.ts                   # API Gateway handler setup
│       │   ├── config_builder.ts               # SPI config builder (operations + endpoints)
│       │   ├── handlers/                       # Lambda entry points (one per operation)
│       │   │   ├── confirm_ticket_handler.ts
│       │   │   ├── get_event_details_handler.ts
│       │   │   ├── get_order_summary_handler.ts
│       │   │   ├── get_seat_details_handler.ts
│       │   │   ├── release_ticket_handler.ts
│       │   │   ├── reserve_ticket_handler.ts
│       │   │   ├── search_events_handler.ts
│       │   │   └── search_venues_handler.ts
│       │   ├── local/
│       │   │   └── server.ts                   # Local testing server
│       │   └── operations/                     # Business logic (what YOU implement)
│       │       ├── confirm_ticket.ts
│       │       ├── get_event_details.ts
│       │       ├── get_order_summary.ts
│       │       ├── get_seat_details.ts
│       │       ├── release_ticket.ts
│       │       ├── reserve_ticket.ts
│       │       ├── search_events.ts
│       │       └── search_venues.ts
│       ├── test-suites/
│       │   └── test-config.json
│       └── tests/
│           ├── api_gateway.test.ts
│           └── search_events.test.ts
├── addon-package/
│   ├── action/
│   │   └── TicketingService-config.json        # SPI config: operations, endpoints
│   └── addon.json                              # Manifest: store listing, permissions, integrations
├── cdk.json
├── jest.config.js
├── package.json
└── tsconfig.json

Implement the SPI handlers

The starter package provides stub implementations with mock data in the action/<category-name>/src/operations directory. You replace the mock logic in these files with real calls to your APIs.

Architecture pattern

Lambda invocation
  → handlers/<operation>_handler.ts    (thin wrapper — don't modify)
    → operations/<operation>.ts        (YOUR business logic)
      → Your backend API

Handler files

The handler file for each operation is in action/<category-name>/src/handlers. These files are generated and you don't normally need to make changes to them.

Copied to clipboard.

// action/food-ordering/src/handlers/get_restaurant_handler.ts

import { getGetRestaurantHandler } from "@alexa-ai/category-food-ordering-spi";
import { APIGatewayProxyHandler } from "aws-lambda";
import { GetRestaurantOperation } from "../operations/get_restaurant";
import { getApiGatewayHandler } from "../apigateway";

export const lambdaHandler: APIGatewayProxyHandler = getApiGatewayHandler(
    getGetRestaurantHandler(GetRestaurantOperation)
);

Copied to clipboard.

// action/home-services/src/handlers/get_services_handler.ts

import { getGetServicesHandler } from "@alexa-ai/category-home-services-spi";
import { APIGatewayProxyHandler } from "aws-lambda";
import { GetServicesOperation } from "../operations/get_services";
import { getApiGatewayHandler } from "../apigateway";

export const lambdaHandler: APIGatewayProxyHandler = getApiGatewayHandler(
    getGetServicesHandler(GetServicesOperation)
);

Copied to clipboard.

// action/local-booking/src/handlers/get_business_handler.ts

import { getGetBusinessHandler } from "@alexa-ai/category-local-booking-spi";
import { APIGatewayProxyHandler } from "aws-lambda";
import { GetBusinessOperation } from "../operations/get_business";
import { getApiGatewayHandler } from "../apigateway";

export const lambdaHandler: APIGatewayProxyHandler = getApiGatewayHandler(
    getGetBusinessHandler(GetBusinessOperation)
);

Copied to clipboard.

// action/restaurant-reservation/src/handlers/get_availability_handler.ts

import { getGetAvailabilityHandler } from "@alexa-ai/category-restaurant-reservation-spi";
import { APIGatewayProxyHandler } from "aws-lambda";
import { GetAvailabilityOperation } from "../operations/get_availability";
import { getApiGatewayHandler } from "../apigateway";

// This is the entry point for the Lambda Function that services the GetAvailabilityOperation
export const lambdaHandler: APIGatewayProxyHandler = getApiGatewayHandler(getGetAvailabilityHandler(GetAvailabilityOperation));

Copied to clipboard.

// action/ticketing/src/handlers/search_events_handler.ts

import { getSearchEventsHandler } from "@alexa-ai/category-ticketing-spi";
import { APIGatewayProxyHandler } from "aws-lambda";
import { SearchEventsOperation } from "../operations/search_events";
import { getApiGatewayHandler } from "../apigateway";

export const lambdaHandler: APIGatewayProxyHandler = getApiGatewayHandler(
    getSearchEventsHandler(SearchEventsOperation)
);

Operation files

For each operation in the relevant SPI, you provide calls to your API. The operation files are in action/<category-name>/src/operations.

Copied to clipboard.

// action/food-ordering/src/operations/get_restaurant.ts

import { Operation } from "@aws-smithy/server-common";
import { GetRestaurantServerInput, GetRestaurantServerOutput } from "@alexa-ai/category-food-ordering-spi";
import { HandlerContext } from "../apigateway";

export const GetRestaurantOperation: Operation<GetRestaurantServerInput, GetRestaurantServerOutput, HandlerContext> =
  async (input, context) => {
    console.log(`Received GetRestaurant from: ${context.user}`);
    console.log(`Input: ${JSON.stringify(input)}`);

    // TODO: Implement actual provider logic to return restaurant details
    return {
      restaurantId: input.restaurantId,
      name: "Example Pizza Place",
      isOpen: true,
      isSpecialInstructionsSupported: true,
      currencyCode: "USD",
      address: {
        city: "Seattle",
        stateOrRegion: "WA",
        countryCode: "US",
        postalCode: "98101",
        formattedAddress: "123 Main St, Seattle, WA 98101",
      },
      fulfillmentAvailabilitySummary: {
        delivery: { available: true },
        pickup: { available: true },
      },
    };
  };

Copied to clipboard.

// action/home-services/src/operations/get_services.ts

import { Operation } from "@aws-smithy/server-common";
import { GetServicesServerInput, GetServicesServerOutput } from "@alexa-ai/category-home-services-spi";
import { HandlerContext } from "../apigateway";

export const GetServicesOperation: Operation<GetServicesServerInput, GetServicesServerOutput, HandlerContext> =
  async (input, context) => {
    console.log(`Received GetServices from: ${context.user}`);
    console.log(`Input: ${JSON.stringify(input)}`);

    // TODO: Implement actual provider logic to return matching services
    return {
      services: [
        {
          id: "svc-001",
          name: "House Cleaning",
          description: "Professional house cleaning service",
          confidence: 0.95,
        },
      ],
    };
  };

Copied to clipboard.

// action/local-booking/src/operations/get_business.ts

import { Operation } from "@aws-smithy/server-common";
import { GetBusinessServerInput, GetBusinessServerOutput } from "@alexa-ai/category-local-booking-spi";
import { HandlerContext } from "../apigateway";

export const GetBusinessOperation: Operation<GetBusinessServerInput, GetBusinessServerOutput, HandlerContext> =
  async (input, context) => {
    console.log(`Received GetBusiness from: ${context.user}`);
    console.log(`Input: ${JSON.stringify(input)}`);

    // TODO: Implement actual provider logic to return business details
    return {
      business: {
        businessId: input.businessId,
        name: "Example Salon",
        description: "Full-service hair salon and spa",
      },
    };
  };

Copied to clipboard.

// action/restaurant-reservation/src/operations/get_availability.ts

import { Operation } from "@aws-smithy/server-common";
import { GetAvailabilityServerInput, GetAvailabilityServerOutput } from "@alexa-ai/category-restaurant-reservation-spi";
import { HandlerContext } from "../apigateway";

// This is the mock implementation of business logic for the GetAvailability operation
export const GetAvailabilityOperation: Operation<GetAvailabilityServerInput, GetAvailabilityServerOutput, HandlerContext> = async (input, context) => {

    console.log(`Received GetAvailability operation from: ${context.user}`);
    console.log(`Restaurant ID: ${input.restaurantId}, Party Size: ${input.partySize}, Start: ${input.startDateTime!}, End: ${input.endDateTime!}`);

    // Input format is YYYY-MM-DDTHH:MM and treat it as PST
    const startTime = input.startDateTime!;
    const endTime = input.endDateTime!;

    // If start time equals end time, return empty array
    if (startTime === endTime) {
        console.log(`Empty time range, returning no slots`);
        return { availableTimeSlots: [] };
    }

    // Extract date from start time for generating slots
    const dateMatch = startTime.match(/^(\d{4}-\d{2}-\d{2})/);
    if (!dateMatch) {
        console.log('Invalid date format in startDateTime');
        return { availableTimeSlots: [] };
    }
    const dateString = dateMatch[1];

    // Generate all possible time slots for the day in PST
    const allSlots = [];

    // Lunch slots: 11:30am to 2pm (every 30 minutes) in PST
    for (let hour = 11; hour <= 13; hour++) {
        for (let minute = (hour === 11 ? 30 : 0); minute < 60; minute += 30) {
            if (hour === 13 && minute > 30) continue; // Stop at 2pm

            const slotTime = `${dateString}T${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;
            allSlots.push({ datetime: slotTime });
        }
    }

    // Dinner slots: 5pm to 10pm (every 30 minutes) in PST
    for (let hour = 17; hour <= 21; hour++) {
        for (let minute = 0; minute < 60; minute += 30) {
            if (hour === 21 && minute > 30) continue; // Stop at 10pm

            const slotTime = `${dateString}T${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;
            allSlots.push({ datetime: slotTime });
        }
    }

    // Filter slots to only include those within the requested time range
    const availableSlots = allSlots.filter(slot => {
        const slotTime = slot.datetime!;
        return slotTime >= startTime && slotTime <= endTime;
    });

    // Limit to 20 slots max
    const limitedSlots = availableSlots.slice(0, 20);

    console.log(`Generated ${limitedSlots.length} available time slots`);
    // Log the available slots for debugging
    if (limitedSlots.length > 0) {
        console.log('Available slots:');
        limitedSlots.forEach(slot => {
            console.log(`- ${slot.datetime!}`);
        });
    } else {
        console.log('No available slots within the requested time range');
    }

    return {
        availableTimeSlots: limitedSlots
    };
};

Copied to clipboard.

// action/ticketing/src/operations/search_events.ts

import { Operation } from "@aws-smithy/server-common";
import { SearchEventsServerInput, SearchEventsServerOutput } from "@alexa-ai/category-ticketing-spi";
import { HandlerContext } from "../apigateway";

export const SearchEventsOperation: Operation<SearchEventsServerInput, SearchEventsServerOutput, HandlerContext> =
  async (input, context) => {
    console.log(`Received SearchEvents from: ${context.user}`);
    console.log(`Input: ${JSON.stringify(input)}`);

    // TODO: Implement actual provider logic to search events
    return {
      events: [
        {
          eventId: "evt-001",
          name: "Example Concert",
          description: "Live music performance",
        },
      ],
    };
  };

See the following tables for the list of operations for each category.

Operation Purpose

GetAvailability

Return available time slots for a restaurant

CreateReservation

Book a reservation

UpdateReservation

Modify an existing reservation

DeleteReservation

Cancel a reservation

For details, see Restaurant Reservation SPI.

Operation Purpose

BatchGetMenuItems

Retrieve detailed information for multiple menu items

CancelPurchase

Cancel an in-progress purchase

CreateCartItem

Add a menu item selection to a cart

CreatePurchase

Create a new purchase (checkout session)

DeleteCartItem

Remove an item from a cart

GetAddresses

Get saved delivery addresses for the user

GetMenuItem

Get details and customization options for a menu item

GetPaymentMethods

Get saved payment methods for the user

GetPurchase

Get details of an in-progress purchase

GetRestaurant

Get details for a specific restaurant

GetRestaurantAvailability

Check restaurant availability for ordering

PreviewPurchase

Generate a checkout preview before submission

RecreatePurchase

Recreate a purchase from a previous order

SearchOrders

Search for placed orders

SearchPurchases

Search for active purchases (in-progress checkouts)

SubmitPurchase

Submit a purchase and place the order

UpdateCartItem

Update a cart item with new selections

UpdatePurchase

Update payment, tip, or fulfillment details

For the full list of operations, see Food Ordering SPI.

Operation Purpose

GetServices

Gets a list of supported services matching the provided project description.

GetServiceSpecification

Retrieves the service specification containing form fields and validation rules that define what customer information Alexa needs to collect for a specific service.

CreateBooking

Creates a new service booking.

ListProfessionals

Lists professionals that offer services for the specified project request.

MessageProfessional

Sends a request to a professional with the project details and customer contact information.

GetUserInfo

Gets information about the signed-in user from their provider account.

GetBooking

Gets an existing booking by the partner's booking identifier.

CancelBooking

Cancels an existing booking.

ModifyBooking

Modifies an existing booking's appointment date and time.

For details, see Home Services SPI.

Operation Purpose

GetBusiness

Get details for a specific business

GetReviews

Get customer reviews for a business

GetServices

Get available services offered by a business

GetAvailability

Check available appointment slots

CreateAppointment

Book a new appointment

UpdateAppointment

Modify an existing appointment

CancelAppointment

Cancel an existing appointment

GetAppointment

Get details of an existing appointment

GetUserInfo

Gets information about the signed-in user from their provider account.

For details, see Local Booking SPI.

Operation Purpose

SearchEvents

Search for events matching criteria

SearchVenues

Search for venues

GetEventDetails

Get details for a specific event

GetSeatDetails

Get available seating information for an event

ReserveTicket

Reserve a ticket temporarily

ConfirmTicket

Confirm a reserved ticket purchase

ReleaseTicket

Release a previously reserved ticket

GetOrderSummary

Get summary of a ticket order

For details, see Ticketing SPI.

Unit testing

The starter package includes test stubs in action/<category-name>/tests/ for each operation. Update these with test cases that cover your actual business logic.

Use the following command to run the tests:

npm test

Add-on configuration

Configure the add-on manifest

Update addon-package/addon.json with your brand information and metadata for your add-on. The following tables show the manifest fields.

Top-level fields

Field Description Required

manifestVersion

Schema version (e.g., "1.0")

Yes

storeListing.distributionCountries

Country codes where add-on is available (only "US" for initial launch)

Yes

storeListing.locales

Locale-keyed store metadata (at least one required; "en-US" for launch)

Yes

permissions

Contains a usage description and the aggregated list of all permission scopes required by the add-on. Not required if your your add-on doesn't require any customer data.

No

permissions.locales

Locale-keyed object containing localizable permission properties. At least one required: en-US for initial launch.

Yes

permissions.locales.<locale>.usageDescription

Description of how your add-on uses the requested customer data. Shown to customers in the consent prompt to help them make an informed decision. Max 200 characters.

Yes

permissions.scopes[].name

Array of scopes. Each scope is an object with a name field.

Yes

integrations

Array of integration entries (at least one required).

Yes

Per-locale fields

These fields are under storeListing.locales.en-US.

Field Description Required

name.value

Display name in Alexa+ Store (max 30 chars)

Yes

name.spokenForm

Voice-friendly invocation name if different from display name (e.g., "Verbo" for VRBO)

No

shortDescription

Summary shown in add-on list (max 123 chars)

Yes (on submit)

fullDescription

Full description with limited markup support: \n, - , **bold** (max 4000 chars)

Yes (on submit)

examplePhrases

Sample utterances to invoke add-on (min 3, max 4 items, max 200 chars each)

Yes (on submit)

recentChanges

Updates made with the latest release

Yes (on submit)

mediaAssets.icons.light

Light theme icons — all sizes required: 72x72, 64x64, 88x88, 126x126, 180x180, 241x241

Yes (on submit)

mediaAssets.icons.dark

Dark theme icons — same sizes as light

No

mediaAssets.carouselImages

Carousel images for store page (600x900 px, max 5, with altText)

Yes (on submit)

mediaAssets.bannerImages

Hero images for mobile/tablet (1200x600 and 1200x300, with altText)

No

privacyAndCompliance.privacyPolicyUrl

HTTPS URL to privacy policy

Yes (on submit)

privacyAndCompliance.termsOfUseUrl

HTTPS URL to terms of use

Yes (on submit)

For your media assets, note the following requirements:

  • You must use an internet-accessible endpoint URL. Alexa copies the content and hosts it during deploy.
  • Logos are mandatory for the Category SDK and must be included for you to deploy your add-on.

Permissions: Pre-declared per template based on SPI requirements.

SPI configuration

The starter package includes the file addon-package/action/<SPI>-config.json. Don't edit this file directly. The alexa-ai deploy command overwrites the contents of this file using config_builder.ts.

Copied to clipboard.

// action/food-ordering/src/config_builder.ts

import { ActionSPIConfigBuilder } from "@alexa-ai/addon-build-plugin";
import { FoodOrderingServiceMetadata, FoodOrderingServiceOperations } from "@alexa-ai/category-food-ordering-spi";

export const actionConfig = new ActionSPIConfigBuilder()
    .name(FoodOrderingServiceMetadata.interfaceName)
    .spiVersion(FoodOrderingServiceMetadata.version)
    .operation(FoodOrderingServiceOperations.BatchGetMenuItems)
    .operation(FoodOrderingServiceOperations.CancelPurchase)
    .operation(FoodOrderingServiceOperations.CreateCartItem)
    .operation(FoodOrderingServiceOperations.CreatePurchase)
    .operation(FoodOrderingServiceOperations.DeleteCartItem)
    .operation(FoodOrderingServiceOperations.GetAddresses)
    .operation(FoodOrderingServiceOperations.GetMenuItem)
    .operation(FoodOrderingServiceOperations.GetPaymentMethods)
    .operation(FoodOrderingServiceOperations.GetPurchase)
    .operation(FoodOrderingServiceOperations.GetRestaurant)
    .operation(FoodOrderingServiceOperations.GetRestaurantAvailability)
    .operation(FoodOrderingServiceOperations.PreviewPurchase)
    .operation(FoodOrderingServiceOperations.RecreatePurchase)
    .operation(FoodOrderingServiceOperations.SearchOrders)
    .operation(FoodOrderingServiceOperations.SearchPurchases)
    .operation(FoodOrderingServiceOperations.SubmitPurchase)
    .operation(FoodOrderingServiceOperations.UpdateCartItem)
    .operation(FoodOrderingServiceOperations.UpdatePurchase)
    .endpoints(ep => ep
        .default("https://food-ordering-api.example.com/prod")
    )
    .build();

Copied to clipboard.

// action/home-services/src/config_builder.ts

import { ActionSPIConfigBuilder } from "@alexa-ai/addon-build-plugin";
import { HomeServicesServiceMetadata, HomeServicesServiceOperations } from "@alexa-ai/category-home-services-spi";

export const actionConfig = new ActionSPIConfigBuilder()
    .name(HomeServicesServiceMetadata.interfaceName)
    .spiVersion(HomeServicesServiceMetadata.version)
    .operation(HomeServicesServiceOperations.ListProfessionals)
    .operation(HomeServicesServiceOperations.MessageProfessional)
    .operation(HomeServicesServiceOperations.GetUserInfo)
    .operation(HomeServicesServiceOperations.GetServices)
    .operation(HomeServicesServiceOperations.GetServiceSpecification)
    .operation(HomeServicesServiceOperations.CreateBooking)
    .operation(HomeServicesServiceOperations.CancelBooking)
    .operation(HomeServicesServiceOperations.ModifyBooking)
    .operation(HomeServicesServiceOperations.GetBooking)
    .endpoints(ep => ep
        .default("https://home-services-api.example.com/prod")
    )
    .build();


Copied to clipboard.

// action/local-booking/src/config_builder.ts

import { ActionSPIConfigBuilder } from "@alexa-ai/addon-build-plugin";
import { LocalBookingServiceMetadata, LocalBookingServiceOperations } from "@alexa-ai/category-local-booking-spi";

export const actionConfig = new ActionSPIConfigBuilder()
    .name(LocalBookingServiceMetadata.interfaceName)
    .spiVersion(LocalBookingServiceMetadata.version)
    .operation(LocalBookingServiceOperations.GetBusiness)
    .operation(LocalBookingServiceOperations.GetReviews)
    .operation(LocalBookingServiceOperations.GetServices)
    .operation(LocalBookingServiceOperations.GetAvailability)
    .operation(LocalBookingServiceOperations.CreateAppointment)
    .operation(LocalBookingServiceOperations.UpdateAppointment)
    .operation(LocalBookingServiceOperations.CancelAppointment)
    .operation(LocalBookingServiceOperations.GetAppointment)
    .operation(LocalBookingServiceOperations.GetUserInfo)
    .endpoints(ep => ep
        .default("https://local-booking-api.example.com/prod")
    )
    .build();

Copied to clipboard.

// action/restaurant-reservation/src/config_builder.ts

import { ActionSPIConfigBuilder } from "@alexa-ai/addon-build-plugin";
import { ReservationServiceMetadata, ReservationServiceOperations } from "@alexa-ai/category-restaurant-reservation-spi"

export const actionConfig = new ActionSPIConfigBuilder()
    .name(ReservationServiceMetadata.interfaceName)
    .spiVersion(ReservationServiceMetadata.version)
    .operation(ReservationServiceOperations.GetAvailability)
    .operation(ReservationServiceOperations.CreateReservation)
    .operation(ReservationServiceOperations.UpdateReservation)
    .operation(ReservationServiceOperations.DeleteReservation)
    .endpoints(ep => ep
        .default("https://rr-api.example.com/prod")
    )
    .build();


Copied to clipboard.

// action/ticketing/src/config_builder.ts

import { ActionSPIConfigBuilder } from "@alexa-ai/addon-build-plugin";
import { TicketingServiceMetadata, TicketingServiceOperations } from "@alexa-ai/category-ticketing-spi";

export const actionConfig = new ActionSPIConfigBuilder()
    .name(TicketingServiceMetadata.interfaceName)
    .spiVersion(TicketingServiceMetadata.version)
    .operation(TicketingServiceOperations.SearchEvents)
    .operation(TicketingServiceOperations.SearchVenues)
    .operation(TicketingServiceOperations.GetEventDetails)
    .operation(TicketingServiceOperations.GetSeatDetails)
    .operation(TicketingServiceOperations.ReserveTicket)
    .operation(TicketingServiceOperations.ConfirmTicket)
    .operation(TicketingServiceOperations.ReleaseTicket)
    .operation(TicketingServiceOperations.GetOrderSummary)
    .endpoints(ep => ep
        .default("https://ticketing-api.example.com/prod")
    )
    .build();

Deploy the add-on

You deploy your add-on through three separate steps.

  1. Deploy your backend infrastructure to AWS with basic authentication enabled for testing purposes.
  2. Deploy your backend infrastructure to AWS with basic authentication disabled.
  3. Deploy your add-on manifest to Alexa+.

Install dependencies and build code

Registry access was configured when you ran the script to configure npm for the private CodeArtifact registry.

Before bootstrapping, review the stack name in action/infrastructure/cdk/bin/app.ts. The default name is shared across all projects created from this template. If multiple add-ons use the same stack name in the same AWS account, they share one CloudFormation stack — updating or destroying it affects all of them. Rename it to something unique, such as MyRestaurantApp-AddOn.

Use the following commands to install project dependencies:

npm install

# Build the project
npm run build

# Bootstrap CDK (first time only)
npx cdk bootstrap

# Bundle code
npm run bundle

Deploy AWS infrastructure for endpoint testing

You deploy your Lambda functions and API Gateway to your own AWS account with basic authentication enabled. This lets you test your API endpoint and validate SPI contract compliance. Your add-on doesn't accept actual Alexa requests during this phase.

Set your basic auth credentials as environment variables:

export BASIC_AUTH_USERNAME='<BASIC_AUTH_USERNAME>'
export BASIC_AUTH_PASSWORD='<BASIC_AUTH_PASSWORD>'
export ALEXA_ADDON_UNDER_TEST=true

# '-c acknowledge-test-mode=true' need explicit consent when ALEXA_ADDON_UNDER_TEST = true 
npx cdk deploy -c acknowledge-test-mode=true

After successful deployment, the terminal outputs your API Gateway endpoint:

Outputs:
ActionStack.ApiGatewayEndpoint = https://abc123def.execute-api.us-west-2.amazonaws.com/prod

Test your endpoint using the Interface Validation Test, as described in Run the interface test tool to validate your SPI implementation.

Deploy AWS infrastructure

Once you have verified that your endpoint works correctly, you can deploy again basic auth disabled.

Run the following command:

# unset env variable related to basic auth
unset ALEXA_ADDON_UNDER_TEST BASIC_AUTH_USERNAME BASIC_AUTH_PASSWORD
npx cdk deploy

Update endpoint configuration

Update action/<category-name>/src/config_builder.ts with your actual endpoint:

.endpoints(ep => ep
    .default("https://abc123def.execute-api.us-west-2.amazonaws.com/prod")
)

Deploy the add-on

Run the following command to deploy your add-on.

alexa-ai deploy

On first deployment, this creates a new add-on ID (stored in .alexa-ai/config.json). Subsequent deployments create new versions.

Use --ignore-hash to force a redeploy even if no manifest changes are detected:

alexa-ai deploy --ignore-hash

Check for library updates

Use standard npm tooling to check for outdated dependencies:

npm outdated

This queries the CodeArtifact registry for newer versions of your @alexa-ai/* dependencies. To update:

npm update @alexa-ai/<package-name>

Alternatively, use your AI coding tool (agentic workflow) to run npm outdated, identify updates, and apply the necessary package.json changes.

After updating dependencies, redeploy: npm run build && npm run deploy && alexa-ai deploy --ignore-hash

Prepare Point of Interest (POI) data

Point of Interest (POI) data is required for categories that involve local search, such as Restaurant Reservations. Test data enables sandboxed (isolated) testing before certification. Note that test catalogs are scoped to the add-on. If multiple developer accounts upload catalogs for the same add-on, the latest upload overrides any previous uploads. All accounts access the same catalog when testing in simulator.

For the accepted POI data format, refer to Point of Interest Catalog Schema.

When you upload your catalog, you use the --usage parameter to specify whether it is test or production data.

Purpose --usage value
Production Alexa.Catalog.PointOfInterest
Test (for isolated testing) Alexa.Catalog.PointOfInterest.Test

Use the test usage during development and isolated testing.

To provide your POI data, you define a catalog and associate it with your add-on. Then, you upload your data. You can upload your data multiple times, overwriting the previous version.

Define a catalog and associate it with your add-on.

Create the test catalog with the create-catalog command.

alexa-ai create-catalog \
    --title "My Restaurant Catalog (Test)" \
    --type AMAZON.PointOfInterest \
    --usage Alexa.Catalog.PointOfInterest.Test \
    --json

Associate catalog with your add-on with the associate-catalog-with-addon command.

alexa-ai associate-catalog-with-addon \
    --addon-id amzn1.alexa-ai.addon.your-id \
    --catalog-id amzn1.ask-catalog.cat.abc123

Upload test data

Upload the data with the upload-catalog command. You can do the upload repeatedly to modify test data. Any new catalog upload replaces the previously uploaded catalog.

alexa-ai upload-catalog \
    --catalog-id amzn1.ask-catalog.cat.abc123 \
    --file ./test-poi-data.json \
    --json

Use the get-content-upload-by-id command to check the status of the upload.

alexa-ai get-content-upload-by-id \
    --catalog-id amzn1.ask-catalog.cat.abc123 \
    --upload-id upload-xyz789 \
    --json

For the full POI schema and field definitions, see Point of Interest Catalog Schema.

Test your add-on

After you deploy, test your add-on to validate your SPI implementation, verify end-to-end functionality, and confirm account linking works correctly. For details, see Test Your Category Action Add-on.

Ingest Production POI Data

Before you submit your add-on for certification, ingest your production POI data. The flow is the same as test data ingestion but uses the production --usage value. (Alexa.Catalog.PointOfInterest)

Create the catalog with the create-catalog command.

alexa-ai create-catalog \
    --title "My Restaurant Catalog" \
    --type AMAZON.PointOfInterest \
    --usage Alexa.Catalog.PointOfInterest \
    --json

Associate catalog with your add-on with the associate-catalog-with-addon command.

alexa-ai associate-catalog-with-addon \
    --addon-id amzn1.alexa-ai.addon.your-id \
    --catalog-id amzn1.ask-catalog.cat.prod123

Upload the data with the upload-catalog command. Upload production data at a regular cadence to ensure catalog is up-to-date.

alexa-ai upload-catalog \
    --catalog-id amzn1.ask-catalog.cat.prod123 \
    --file ./production-poi-data.json \
    --json

Use the get-content-upload-by-id command to check the status of your upload.

alexa-ai get-content-upload-by-id \
    --catalog-id amzn1.ask-catalog.cat.prod123 \
    --upload-id <upload-id-from-step-2> \
    --json

Production catalogs use --usage Alexa.Catalog.PointOfInterest instead of Alexa.Catalog.PointOfInterest.Test. Production POI data is required before certification for categories involving local search.

Submit your add-on for certification

See Alexa+ Add-on Certification Guidelines.


Was this page helpful?

Last updated: Jul 10, 2026