SMAPI Developer Client¶
The SMAPI (Skill Management API) Developer Client enables programmatic management of Alexa skills from CI/CD pipelines and external tooling. It uses Login with Amazon (LWA) OAuth refresh token authentication with credentials obtained from the Amazon Developer Console.
Prerequisites¶
Before using the SMAPI Developer Client, you need:
- Amazon Developer Account - Register at developer.amazon.com
- Security Profile - Create a security profile in your Amazon Developer Console
- ASK CLI - Install the Alexa Skills Kit CLI for token generation
Getting Credentials¶
Step 1: Create a Security Profile¶
- Go to the Amazon Developer Console
- Click Create a New Security Profile
- Fill in the required fields:
- Security Profile Name: e.g., "SMAPI CI/CD Integration"
- Security Profile Description: Describe its purpose
- Consent Privacy Notice URL: Your privacy policy URL
- Save the security profile
- Navigate to Web Settings and note the Client ID and Client Secret
Step 2: Generate LWA Tokens¶
Use the ASK CLI to generate Login with Amazon tokens:
# Install ASK CLI if not already installed
npm install -g ask-cli
# Configure ASK CLI
ask configure
# Generate LWA tokens with required scopes
ask util generate-lwa-tokens \
--client-id YOUR_CLIENT_ID \
--client-confirmation YOUR_CLIENT_SECRET \
--scopes "SPACE_SEPARATED_SCOPES"
This command opens a browser for authorization and returns the access token and refresh token. Save the refresh token securely - this is the long-lived credential you'll use for CI/CD authentication.
For available scopes, see the SMAPI Access Token Scopes documentation.
Configuration¶
Using appsettings.json¶
Add the following to your appsettings.json:
{
"SmapiClient": {
"ClientId": "amzn1.application-oa2-client.xxxxx",
"ClientSecret": "your-client-secret",
"RefreshToken": "Atzr|xxxxx"
}
}
Security Notice
Never commit credentials to source control. Use environment variables, Azure Key Vault, AWS Secrets Manager, or other secret management solutions for production deployments.
Using Environment Variables¶
export SmapiClient__ClientId="amzn1.application-oa2-client.xxxxx"
export SmapiClient__ClientSecret="your-client-secret"
export SmapiClient__RefreshToken="Atzr|xxxxx"
Registration¶
Configuration-Based Registration¶
using AlexaVoxCraft.Smapi;
var builder = WebApplication.CreateBuilder(args);
// Register with default section name "SmapiClient"
builder.Services.AddSmapiDeveloperClient(builder.Configuration);
// Or with a custom section name
builder.Services.AddSmapiDeveloperClient(builder.Configuration, "AlexaSkillManagement");
Action-Based Registration¶
using AlexaVoxCraft.Smapi;
builder.Services.AddSmapiDeveloperClient(options =>
{
options.ClientId = "amzn1.application-oa2-client.xxxxx";
options.ClientSecret = Environment.GetEnvironmentVariable("SMAPI_CLIENT_SECRET")!;
options.RefreshToken = Environment.GetEnvironmentVariable("SMAPI_REFRESH_TOKEN")!;
});
Usage¶
Interaction Model Client¶
The IAlexaInteractionModelClient provides methods for managing skill interaction models:
public class SkillDeploymentService
{
private readonly IAlexaInteractionModelClient _client;
public SkillDeploymentService(IAlexaInteractionModelClient client)
{
_client = client;
}
public async Task<string> UpdateInteractionModelAsync(
string skillId,
string locale,
InteractionModelDefinition model,
CancellationToken cancellationToken = default)
{
return await _client.UpdateInteractionModelAsync(
skillId,
"development",
locale,
model,
cancellationToken);
}
public async Task<InteractionModelDefinition?> GetInteractionModelAsync(
string skillId,
string locale,
CancellationToken cancellationToken = default)
{
return await _client.GetInteractionModelAsync(
skillId,
"development",
locale,
cancellationToken);
}
}
Building Interaction Models¶
Use the fluent builder API to construct interaction models:
using AlexaVoxCraft.Smapi.Builders.InteractionModel;
using AlexaVoxCraft.Model.Request.Type;
var model = InteractionModelBuilder.Create()
.WithInvocationName("my skill")
.WithVersion("1")
.WithDescription("My skill interaction model")
.AddIntent("OrderPizzaIntent", intent =>
intent.WithSlot("size", "PizzaSize")
.WithSlot("topping", "PizzaTopping")
.WithSamples(
"order a {size} pizza",
"I want a {size} {topping} pizza",
"order pizza"))
.AddIntent(BuiltInIntent.Help)
.AddIntent(BuiltInIntent.Cancel)
.AddIntent(BuiltInIntent.Stop)
.AddSlotType("PizzaSize", type =>
type.WithValue("small", v => v.WithSynonyms("little", "mini"))
.WithValue("medium", v => v.WithSynonyms("regular", "normal"))
.WithValue("large", v => v.WithSynonyms("big", "extra large")))
.AddSlotType("PizzaTopping", type =>
type.WithValue("pepperoni")
.WithValue("mushroom", v => v.WithSynonyms("mushrooms"))
.WithValue("cheese", v => v.WithSynonyms("plain")))
.Build();
Name-Free Interactions¶
Name-Free Interaction (NFI) allows users to interact with your skill without explicitly invoking its name. Configure NFI using the fluent builder API:
using AlexaVoxCraft.Smapi.Builders.InteractionModel;
var model = InteractionModelBuilder.Create()
.WithInvocationName("coffee shop")
.WithVersion("1")
.WithDescription("Coffee shop skill with name-free interactions")
.AddIntent("OrderIntent", intent =>
intent.WithSlot("drink", "DrinkType")
.WithSamples("order {drink}", "buy {drink}"))
.AddSlotType("DrinkType", type =>
type.WithValue("coffee")
.WithValue("tea")
.WithValue("latte"))
.WithNameFreeInteraction(nfi => nfi
.WithLaunchIngressPoint(launch => launch
.WithUtterances("what's available", "show menu", "what can I order"))
.WithIntentIngressPoint("OrderIntent", intent => intent
.WithUtterances("order coffee", "get tea", "buy a latte")))
.Build();
NFI Builder Methods¶
Launch Ingress Points - Utterances that invoke the skill without the skill name:
.WithLaunchIngressPoint(launch => launch
.WithUtterance("what's new")
.WithUtterances("latest updates", "recent changes"))
Intent Ingress Points - Utterances that map directly to intents:
Custom Utterance Formats - Specify format for special utterance processing:
Multiple Ingress Points - Combine launch and multiple intent ingress points:
.WithNameFreeInteraction(nfi => nfi
.WithLaunchIngressPoint(launch => launch
.WithUtterances("start", "begin"))
.WithIntentIngressPoint("OrderIntent", order => order
.WithUtterances("order", "buy"))
.WithIntentIngressPoint("StatusIntent", status => status
.WithUtterance("check status")))
JSON Serialization¶
Export the interaction model as JSON for debugging or manual deployment:
var json = InteractionModelBuilder.Create()
.WithInvocationName("my skill")
// ... configure model
.ToJson();
Console.WriteLine(json);
CI/CD Integration¶
GitHub Actions Example¶
name: Deploy Interaction Model
on:
push:
branches: [main]
paths:
- 'interaction-models/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Deploy Interaction Model
env:
SmapiClient__ClientId: ${{ secrets.SMAPI_CLIENT_ID }}
SmapiClient__ClientSecret: ${{ secrets.SMAPI_CLIENT_SECRET }}
SmapiClient__RefreshToken: ${{ secrets.SMAPI_REFRESH_TOKEN }}
run: dotnet run --project tools/DeployInteractionModel
Azure DevOps Pipeline Example¶
trigger:
branches:
include:
- main
paths:
include:
- interaction-models/*
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
inputs:
version: '9.0.x'
- task: DotNetCoreCLI@2
displayName: 'Deploy Interaction Model'
inputs:
command: 'run'
projects: 'tools/DeployInteractionModel/DeployInteractionModel.csproj'
env:
SmapiClient__ClientId: $(SMAPI_CLIENT_ID)
SmapiClient__ClientSecret: $(SMAPI_CLIENT_SECRET)
SmapiClient__RefreshToken: $(SMAPI_REFRESH_TOKEN)
Token Management¶
The SMAPI Developer Client automatically handles token refresh. Access tokens are cached and refreshed when they expire (with a small buffer to prevent edge cases). You don't need to manage tokens manually.
Error Handling¶
The client throws standard HTTP exceptions for API errors:
try
{
await client.UpdateInteractionModelAsync(skillId, stage, locale, model);
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
// Skill or locale not found
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
// Authentication failed - check credentials
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.TooManyRequests)
{
// Rate limited - implement retry with backoff
}