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.
- Initialize a new project for your intended category. This provides you with the initial starter code. See Initialize your add-on project.
- Implement the Service Provider Interfaces (SPIs) to call your APIs. See Implement the SPI handlers.
- Configure the add-on manifest file with required metadata for your add-on. See Configure the add-on manifest.
- Configure account linking if needed. Account linking is required for operations that modify user data.
- Build and deploy the add-on for testing. See Deploy the add-on.
- 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
- 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.
- Submit the add-on for certification. See Certification
The following diagram shows how category add-ons work.

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.
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.
// 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)
);
// 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)
);
// 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)
);
// 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));
// 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.
// 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 },
},
};
};
// 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,
},
],
};
};
// 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",
},
};
};
// 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
};
};
// 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 |
|---|---|
|
|
Return available time slots for a restaurant |
|
|
Book a reservation |
|
|
Modify an existing reservation |
|
|
Cancel a reservation |
For details, see Restaurant Reservation SPI.
| Operation | Purpose |
|---|---|
|
|
Retrieve detailed information for multiple menu items |
|
|
Cancel an in-progress purchase |
|
|
Add a menu item selection to a cart |
|
|
Create a new purchase (checkout session) |
|
|
Remove an item from a cart |
|
|
Get saved delivery addresses for the user |
|
|
Get details and customization options for a menu item |
|
|
Get saved payment methods for the user |
|
|
Get details of an in-progress purchase |
|
|
Get details for a specific restaurant |
|
|
Check restaurant availability for ordering |
|
|
Generate a checkout preview before submission |
|
|
Recreate a purchase from a previous order |
|
|
Search for placed orders |
|
|
Search for active purchases (in-progress checkouts) |
|
|
Submit a purchase and place the order |
|
|
Update a cart item with new selections |
|
|
Update payment, tip, or fulfillment details |
For the full list of operations, see Food Ordering SPI.
| Operation | Purpose | |
|---|---|---|
|
|
Gets a list of supported services matching the provided project description. | |
|
|
Retrieves the service specification containing form fields and validation rules that define what customer information Alexa needs to collect for a specific service. | |
|
|
Creates a new service booking. | |
|
|
Lists professionals that offer services for the specified project request. | |
|
|
Sends a request to a professional with the project details and customer contact information. | |
|
|
Gets information about the signed-in user from their provider account. | |
|
|
Gets an existing booking by the partner's booking identifier. | |
|
|
| |
|
|
Modifies an existing booking's appointment date and time. |
For details, see Home Services SPI.
| Operation | Purpose |
|---|---|
|
|
Get details for a specific business |
|
|
Get customer reviews for a business |
|
|
Get available services offered by a business |
|
|
Check available appointment slots |
|
|
Book a new appointment |
|
|
Modify an existing appointment |
|
|
Cancel an existing appointment |
|
|
Get details of an existing appointment |
|
|
Gets information about the signed-in user from their provider account. |
For details, see Local Booking SPI.
| Operation | Purpose |
|---|---|
|
|
Search for events matching criteria |
|
|
Search for venues |
|
|
Get details for a specific event |
|
|
Get available seating information for an event |
|
|
Reserve a ticket temporarily |
|
|
Confirm a reserved ticket purchase |
|
|
Release a previously reserved ticket |
|
|
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.
alexa-ai deploy during development but must be populated before alexa-ai submit for certification.
Top-level fields
| Field | Description | Required |
|---|---|---|
|
|
Schema version (e.g., |
Yes |
|
|
Country codes where add-on is available (only |
Yes |
|
|
Locale-keyed store metadata (at least one required; |
Yes |
|
|
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 |
|
|
Locale-keyed object containing localizable permission properties. At least one required: |
Yes |
|
|
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 |
|
|
Array of scopes. Each scope is an object with a |
Yes |
|
|
Array of integration entries (at least one required). |
Yes |
Per-locale fields
These fields are under storeListing.locales.en-US.
| Field | Description | Required |
|---|---|---|
|
|
Display name in Alexa+ Store (max 30 chars) |
Yes |
|
|
Voice-friendly invocation name if different from display name (e.g., "Verbo" for VRBO) |
No |
|
|
Summary shown in add-on list (max 123 chars) |
Yes (on submit) |
|
|
Full description with limited markup support: |
Yes (on submit) |
|
|
Sample utterances to invoke add-on (min 3, max 4 items, max 200 chars each) |
Yes (on submit) |
|
|
Updates made with the latest release |
Yes (on submit) |
|
|
Light theme icons — all sizes required: 72x72, 64x64, 88x88, 126x126, 180x180, 241x241 |
Yes (on submit) |
|
|
Dark theme icons — same sizes as light |
No |
|
|
Carousel images for store page (600x900 px, max 5, with |
Yes (on submit) |
|
|
Hero images for mobile/tablet (1200x600 and 1200x300, with |
No |
|
|
HTTPS URL to privacy policy |
Yes (on submit) |
|
|
HTTPS URL to terms of use |
Yes (on submit) |
mediaAssets, as these are required for deployment. You can replace these icons with your own logos.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.
// 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();
// 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();
// 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();
// 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();
// 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.
- Deploy your backend infrastructure to AWS with basic authentication enabled for testing purposes.
- Deploy your backend infrastructure to AWS with basic authentication disabled.
- 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.
Related topics
- Overview of the Category SDK
- Set Up Your Development Environment
- Alexa+ Add-ons
- Home Services SPI
- Restaurant Reservation SPI
- Food Ordering SPI
- Local Booking SPI
- Ticketing SPI
- Alexa AI CLI Reference
Last updated: Jul 10, 2026

