Add GET, POST, PATCH, and DELETE operations to a TypeSpec API plugin with proper routing, parameters, and adaptive cards
Install
npx skills add https://github.com/github/awesome-copilot --skill typespec-api-operationsSKILL.md
Add TypeSpec API Operations
Add RESTful operations to an existing TypeSpec API plugin for Microsoft 365 Copilot.
Adding GET Operations
Simple GET - List All Items
/**
* List all items.
*/
@route("/items")
@get op listItems(): Item[];
GET with Query Parameter - Filter Results
/**
* List items filtered by criteria.
* @param userId Optional user ID to filter items
*/
@route("/items")
@get op listItems(@query userId?: integer): Item[];
GET with Path Parameter - Get Single Item
/**
* Get a specific item by ID.
* @param id The ID of the item to retrieve
*/
@route("/items/{id}")
@get op getItem(@path id: integer): Item;
GET with Adaptive Card
/**
* List items with adaptive card visualization.
*/
@route("/items")
@card(#{
dataPath: "$",
title: "$.title",
file: "item-card.json"
})
@get op listItems(): Item[];
Create the Adaptive Card (appPackage/item-card.json):
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5",
"body": [
{
"type": "Container",
"$data": "${$root}",
"items": [
{
"type": "TextBlock",
"text": "**${if(title, title, 'N/A')}**",
"wrap": true
},
{
"type": "TextBlock",
"text": "${if(description, description, 'N/A')}",
"wrap": true
}
]
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "View Details",
"url": "https://example.com/items/${id}"
}
]
}
Adding POST Operations
Simple POST - Create Item
/**
* Create a new item.
* @param item The item to create
*/
@route("/items")
@post op createItem(@body item: CreateItemRequest): Item;
model CreateItemRequest {
title: string;
description?: string;
userId: integer;
}
POST with Confirmation
/**
* Create a new item with confirmation.
*/
@route("/items")
@post
@capabilities(#{
confirmation: #{
type: "AdaptiveCard",
title: "Create Item",
body: """
Are you sure you want to create this item?
* **Title**: {{ function.parameters.item.title }}
* **User ID**: {{ function.parameters.item.userId }}
"""
}
})
op createItem(@body item: CreateItemRequest): Item;
Adding PATCH Operations
Simple PATCH - Update Item
/**
* Update an existing item.
* @param id The ID of the item to update
* @param item The updated item data
*/
@route("/items/{id}")
@patch op updateItem(
@path id: integer,
@body item: UpdateItemRequest
): Item;
model UpdateItemRequest {
title?: string;
description?: string;
status?: "active" | "completed" | "archived";
}
PATCH with Confirmation
/**
* Update an item with confirmation.
*/
@route("/items/{id}")
@patch
@capabilities(#{
confirmation: #{
type: "AdaptiveCard",
title: "Update Item",
body: """
Updating item #{{ function.parameters.id }}:
* **Title**: {{ function.parameters.item.title }}
* **Status**: {{ function.parameters.item.status }}
"""
}
})
op updateItem(
@path id: integer,
@body item: UpdateItemRequest
): Item;
Adding DELETE Operations
Simple DELETE
/**
* Delete an item.
* @param id The ID of the item to delete
*/
@route("/items/{id}")
@delete op deleteItem(@path id: integer): void;
DELETE with Confirmation
/**
* Delete an item with confirmation.
*/
@route("/items/{id}")
@delete
@capabilities(#{
confirmation: #{
type: "AdaptiveCard",
title: "Delete Item",
body: """
⚠️ Are you sure you want to delete item #{{ function.parameters.id }}?
This action cannot be undone.
"""
}
})
op deleteItem(@path id: integer): void;
Complete CRUD Example
Define the Service and Models
@service
@server("https://api.example.com")
@actions(#{
nameForHuman: "Items API",
descriptionForHuman: "Manage items",
descriptionForModel: "Read, create, update, and delete items"
})
namespace ItemsAPI {
// Models
model Item {
@visibility(Lifecycle.Read)
id: integer;
userId: integer;
title: string;
description?: string;
status: "active" | "completed" | "archived";
@format("date-time")
createdAt: utcDateTime;
@format("date-time")
updatedAt?: utcDateTime;
}
model CreateItemRequest {
userId: integer;
title: string;
description?: string;
}
model UpdateItemRequest {
title?: string;
description?: string;
status?: "active" | "completed" | "archived";
}
// Operations
@route("/items")
@card(#{ dataPath: "$", title: "$.title", file: "item-card.json" })
@get op listItems(@query userId?: integer): Item[];
@route("/items/{id}")
@card(#{ dataPath: "$", title: "$.title", file: "item-card.json" })
@get op getItem(@path id: integer): Item;
@route("/items")
@post
@capabilities(#{
confirmation: #{
type: "AdaptiveCard",
title: "Create Item",
body: "Creating: **{{ function.parameters.item.title }}**"
}
})
op createItem(@body item: CreateItemRequest): Item;
@route("/items/{id}")
@patch
@capabilities(#{
confirmation: #{
type: "AdaptiveCard",
title: "Update Item",
body: "Updating item #{{ function.parameters.id }}"
}
})
op updateItem(@path id: integer, @body item: UpdateItemRequest): Item;
@route("/items/{id}")
@delete
@capabilities(#{
confirmation: #{
type: "AdaptiveCard",
title: "Delete Item",
body: "⚠️ Delete item #{{ function.parameters.id }}?"
}
})
op deleteItem(@path id: integer): void;
}
Advanced Features
Multiple Query Parameters
@route("/items")
@get op listItems(
@query userId?: integer,
@query status?: "active" | "completed" | "archived",
@query limit?: integer,
@query offset?: integer
): ItemList;
model ItemList {
items: Item[];
total: integer;
hasMore: boolean;
}
Header Parameters
@route("/items")
@get op listItems(
@header("X-API-Version") apiVersion?: string,
@query userId?: integer
): Item[];
Custom Response Models
@route("/items/{id}")
@delete op deleteItem(@path id: integer): DeleteResponse;
model DeleteResponse {
success: boolean;
message: string;
deletedId: integer;
}
Error Responses
model ErrorResponse {
error: {
code: string;
message: string;
details?: string[];
};
}
@route("/items/{id}")
@get op getItem(@path id: integer): Item | ErrorResponse;
Testing Prompts
After adding operations, test with these prompts:
GET Operations:
- "List all items and show them in a table"
- "Show me items for user ID 1"
- "Get the details of item 42"
POST Operations:
- "Create a new item with title 'My Task' for user 1"
- "Add an item: title 'New Feature', description 'Add login'"
PATCH Operations:
- "Update item 10 with title 'Updated Title'"
- "Change the status of item 5 to completed"
DELETE Operations:
- "Delete item 99"
- "Remove the item with ID 15"
Best Practices
Parameter Naming
- Use descriptive parameter names:
userIdnotuid - Be consistent across operations
- Use optional parameters (
?) for filters
Documentation
- Add JSDoc comments to all operations
- Describe what each parameter does
- Document expected responses
Models
- Use
@visibility(Lifecycle.Read)for read-only fields likeid - Use
@format("date-time")for date fields - Use union types for enums:
"active" | "completed" - Make optional fields explicit with
?
Confirmations
- Always add confirmations to destructive operations (DELETE, PATCH)
- Show key details in confirmation body
- Use warning emoji (⚠️) for irreversible actions
Adaptive Cards
- Keep cards simple and focused
- Use conditional rendering with
${if(..., ..., 'N/A')} - Include action buttons for common next steps
- Test data binding with actual API responses
Routing
- Use RESTful conventions:
GET /items- ListGET /items/{id}- Get onePOST /items- CreatePATCH /items/{id}- UpdateDELETE /items/{id}- Delete
- Group related operations in the same namespace
- Use nested routes for hierarchical resources
Common Issues
Issue: Parameter not showing in Copilot
Solution: Check parameter is properly decorated with @query, @path, or @body
Issue: Adaptive card not rendering
Solution: Verify file path in @card decorator and check JSON syntax
Issue: Confirmation not appearing
Solution: Ensure @capabilities decorator is properly formatted with confirmation object
Issue: Model property not appearing in response
Solution: Check if property needs @visibility(Lifecycle.Read) or remove it if it should be writable
Related skills
entra-app-registrationmicrosoft606KGuides Microsoft Entra ID app registration, OAuth 2.0 authentication, and MSAL integration. USE FOR: create app registration, register Azure AD app, configure OAuth, set up authentication, add API permissions, generate service principal, MSAL example, console app auth, Entra ID setup, Azure AD authentication. DO NOT USE FOR: Key Vault secrets (use azure-keyvault-expiration-audit), general Azure resource security guidance.azure-messagingmicrosoft595KTroubleshoot and resolve issues with Azure Messaging SDKs for Event Hubs and Service Bus. Covers connection failures, authentication errors, message processing issues, and SDK configuration problems. WHEN: event hub SDK error, service bus SDK issue, messaging connection failure, AMQP error, event processor host issue, message lock lost, message lock expired, lock renewal, lock renewal batch, send timeout, receiver disconnected, SDK troubleshooting, azure messaging SDK, event hub consumer, servicentra-agent-idmicrosoft328KProvision Microsoft Entra Agent Identity Blueprints, BlueprintPrincipals, and per-instance Agent Identities via Microsoft Graph, and configure OAuth 2.0 token exchange (fmi_path, OBO, cross-tenant) including the Microsoft Entra SDK for AgentID sidecar. USE FOR: Agent Identity Blueprint, BlueprintPrincipal, agent OAuth, fmi_path token exchange, agent OBO, Workload Identity Federation for agents, polyglot agent auth, Microsoft.Identity.Web.AgentIdentities. DO NOT USE FOR: standard Entra app registsupabasesupabase298KUse when doing ANY task involving Supabase. Triggers: Supabase products (Database, Auth, Edge Functions, Realtime, Storage, Vectors, Cron, Queues); client libraries and SSR integrations (supabase-js, @supabase/ssr) in Next.js, React, SvelteKit, Astro, Remix; auth issues (login, logout, sessions, JWT, cookies, getSession, getUser, getClaims, RLS); Supabase CLI or MCP server; schema changes, migrations, declarative schemas, security audits, Postgres extensions (pg_graphql, pg_cron, pg_vector); deb