Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Add GET, POST, PATCH, and DELETE operations to a TypeSpec API plugin with proper routing, parameters, and adaptive cards
.claude/skills/typespec-api-operations/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-08 | ✗→✓ | ▲ Improved | — | — |
| case-12 | ✗→✓ | ▲ Improved | — | — |
| case-18 | ✗→✓ | ▲ Improved | — | — |
| case-19 | ✗→✓ | ▲ Improved | — | — |
| case-05 | ✗→✓ | ▲ Improved | — | — |
Add RESTful operations to an existing TypeSpec API plugin for Microsoft 365 Copilot.
typescript/** * List all items. */ @route("/items") @get op listItems(): Item[];
typescript/** * List items filtered by criteria. * @param userId Optional user ID to filter items */ @route("/items") @get op listItems(@query userId?: integer): Item[];
typescript/** * 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;
typescript/** * 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):
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}" } ] }
typescript/** * 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; }
typescript/** * 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;
typescript/** * 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"; }
typescript/** * 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;
typescript/** * Delete an item. * @param id The ID of the item to delete */ @route("/items/{id}") @delete op deleteItem(@path id: integer): void;
typescript/** * 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;
typescript@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; }
typescript@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; }
typescript@route("/items") @get op listItems( @header("X-API-Version") apiVersion?: string, @query userId?: integer ): Item[];
typescript@route("/items/{id}") @delete op deleteItem(@path id: integer): DeleteResponse; model DeleteResponse { success: boolean; message: string; deletedId: integer; }
typescriptmodel ErrorResponse { error: { code: string; message: string; details?: string[]; }; } @route("/items/{id}") @get op getItem(@path id: integer): Item | ErrorResponse;
After adding operations, test with these prompts:
GET Operations:
POST Operations:
PATCH Operations:
DELETE Operations:
userId not uid?) for filters@visibility(Lifecycle.Read) for read-only fields like id@format("date-time") for date fields"active" | "completed"?${if(..., ..., 'N/A')}GET /items - ListGET /items/{id} - Get onePOST /items - CreatePATCH /items/{id} - UpdateDELETE /items/{id} - DeleteSolution: Check parameter is properly decorated with @query, @path, or @body
Solution: Verify file path in @card decorator and check JSON syntax
Solution: Ensure @capabilities decorator is properly formatted with confirmation object
Solution: Check if property needs @visibility(Lifecycle.Read) or remove it if it should be writable
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +41 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.