Declarative Agents: Azure OpenAI API Key Auth with TypeSpec
Table of Contents
- Introduction
- Use Cases
- Step-by-Step: Create Agent with TypeSpec
- Limitations & Gotchas
- Troubleshooting Build Errors
- main.tsp Solution for OpenAI
- Action.tsp Example
- Output: Generated Files
- Testing
- Tips for Success
- Declarative Agent - Volunteering App: MS Graph Auth Challenge
- Conclusion
- References
Introduction
Building declarative agents using the M365 Agents Toolkit and TypeSpec streamlines the process of generating JSON and YAML specifications. This is a great improvement to manually updating and generating JSON and YAML yourself which is not the best experience. Typescript abstracts the process providing an easier way to define the API specification file and plugin specification in an easier to understand language (consider it as TypeScript for APIs). Refer to TypeSpec 101: Building Microsoft 365 Declarative Agents for more info. This guide walks through creating an agent with API key authentication (thanks to the example how to add API key authenticaion from Ejaz Hussein’s post Integrating Microsoft Copilot Declarative Agents with Azure AI Search - A TypeSpec Approach), common pitfalls, and troubleshooting tips for Azure Open API. You can adapt the same concepts to call Azure AI Search.
Use Cases
- Calling Azure OpenAI
- Calling Azure AI Search
Step-by-Step: Create Agent with TypeSpec
- Start with M365 Agents Toolkit
- Pick the option
Create Agent with TypeSpec. 
- Pick the option
The scaffoled solution is

- Modify the scaffolded files within the solution
To call an existing Azure Open AI resource, note down the API Key and end point to replace in the action.tsp and env.dev files
main.tsp Solution for OpenAI
Amend sample instructions as per your need.
// Import required TypeSpec modules
import "@typespec/http";
import "@typespec/openapi3";
import "@microsoft/typespec-m365-copilot";
import "./actions.tsp";
using TypeSpec.Http;
using TypeSpec.M365.Copilot.Agents;
using TypeSpec.M365.Copilot.Actions;
@agent(
"OpenAITypeSpec",
"Declarative agent created with Microsoft 365 Agents Toolkit and TypeSpec for Microsoft 365 Copilot."
)
@instructions("""
You can call the `searchVolunteerOpportunities` API to get a list of available volunteering opportunities.
Ensure you provide the necessary authentication token and any required query parameters as specified in the API documentation
**Example API Request**
Here’s an example of the JSON payload sent to Azure OpenAI replacing <Prompt> with user prompt:
{
"messages": [
{
"role": "system",
"content": [
{
"type": "text",
"text": "You are a helpful assistant helping to gather volunteering opportunities"
}
]
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "<Prompt>"
}
]
}
],
"temperature": 0.7,
"top_p": 0.95,
"max_tokens": 800
}
""")
namespace OpenAITypeSpec {
@service
@server(global.AzureOpenAPI.SERVER_URL)
@actions(global.AzureOpenAPI.ACTIONS_METADATA)
namespace AzureOpenAPIActions {
op searchVolunteerOpportunities is global.AzureOpenAPI.searchVolunteerOpportunities;
}
}
// This sample agent enables searching for volunteer opportunities using OpenAI.
Action.tsp Example
Replace the following hard coded values as per your need
const SERVER_URL = "https://resopenai22082025.openai.azure.com";
const MODEL = "gpt-4o";
const TEMPERATURE = 0.7;
const TOP_P = 0.95;
const MAX_TOKENS = 1024;
const PRESENCE_PENALTY = 0.0;
const FREQUENCY_PENALTY = 0.0;
Full code is
// Import required TypeSpec modules
import "@typespec/http";
import "@microsoft/typespec-m365-copilot";
using TypeSpec.Http;
using TypeSpec.M365.Copilot.Actions;
@service
@server(AzureOpenAPI.SERVER_URL)
@actions(AzureOpenAPI.ACTIONS_METADATA)
namespace AzureOpenAPI {
/**
* Metadata for the AzureOpenAPI API actions.
*/
const ACTIONS_METADATA = #{
nameForHuman: "AzureOpenAPI",
descriptionForHuman: "Query AzureOpenAPI.",
descriptionForModel: "Query AzureOpenAPI.",
};
/**
* The base URL for the AzureOpenAPI API.
*/
const SERVER_URL = "https://resopenai22082025.openai.azure.com";
const MODEL = "gpt-4o";
const TEMPERATURE = 0.7;
const TOP_P = 0.95;
const MAX_TOKENS = 1024;
const PRESENCE_PENALTY = 0.0;
const FREQUENCY_PENALTY = 0.0;
@doc("Searches for volunteering opportunities using Azure Open API")
@useAuth(ApiKeyAuth<ApiKeyLocation.header, "api-key">)
@route("/openai/deployments/${MODEL}/chat/completions")
@post
op searchVolunteerOpportunities(
@body post: ChatCompletionsRequest,
@query("api-version") apiVersion: string = "2025-01-01-preview",
): ChatCompletionsResponse;
// Request and response models for chat completions
model ChatCompletionsRequest {
messages: Message[];
temperature?: float32 = TEMPERATURE;
top_p?: float32 = TOP_P;
max_tokens?: int32 = MAX_TOKENS;
n?: int32 = 1;
stop?: string[];
presence_penalty?: float32 = PRESENCE_PENALTY;
frequency_penalty?: float32 = FREQUENCY_PENALTY;
}
model Message {
role: "system" | "user" | "assistant";
content: Content[];
}
model Content {
type: "text";
text: string
}
model ChatCompletionsResponse {
id: string;
choices: Choice[];
usage: Usage;
}
model Choice {
text: string;
finish_reason: string;
}
model Usage {
prompt_tokens: int32;
completion_tokens: int32;
total_tokens: int32;
}
model ErrorResponse {
message?: string
}
}
env file
Add the key APIKEYAUTH_REGISTRATION_ID to the env file.
APIKEYAUTH_REGISTRATION_ID=0f1bae1c-469e-4cac-8b3e-9ed4e42b5dbe
Handle Authentication
If the above key is not specified and your actions have auth defined, you’ll be prompted to create a registration ID for it if not already defined in the environment as above.

Limitations & Gotchas
Summary of Key Limitations:
- Cannot reference environment variables directly in actions file
- Only one plugin with auth is supported easily; multi-auth is challenging or missing
- No clear way to reference external instructions files; some characters (e.g.,
\) are not allowed
Details:
Environment Variables
- You can’t directly reference environment variables in the actions file similar to directly within the .yaml file for the API Specification. The generated .yaml and .json are read only and can’t be modified post generation.
Multiple Plugins with Defined Authentication
- I was able to add only one plugin with auth, not sure how multiple plugins with authentication would work specially if all refers to a different key.
Instructions File
- I was unable to find a way to reference an external instructions file, so I had to hard-code the instructions directly into the main.tsp file. Additionally, certain characters—such as the ‘' escape character—are not allowed, which makes it difficult to include more complex or lengthy instructions.
Troubleshooting Build Errors
If you encounter errors and all error information is not rendered within the Terminal, use the command below instead:
npx --package=@typespec/compiler tsp compile ./main.tsp --config ./tspconfig.yaml

- Fix all errors before provisioning. For example, ensure you use the correct syntax for API key authentication:
@useAuth(ApiKeyAuth<ApiKeyLocation.header, "api-key">)
// This line configures API key authentication in the header for your agent.
- To bypass the auto creation of the key registrations in the Teams developer portal, you can manually add an existing key in the file.
- Define the key within the
.envfile:
Output: Generated Files
After provisioning, you’ll get the JSON and YAML files:

Please note that the generated files are read only and can’t be modified.
Testing

I did the same with Azure AI Search following the instructions from Ejaz Hussein’s post Integrating Microsoft Copilot Declarative Agents with Azure AI Search - A TypeSpec Approach using an existing Azure AI Search and voila

Tips for Success
- Always validate your TypeSpec syntax and authentication setup before provisioning.
- Keep instructions concise to avoid character issues.
- Test the generated files with your agent runtime to ensure compatibility.
- Use official samples as a starting point and adapt for your business needs.
Declarative Agent - Volunteering App: MS Graph Auth Challenge
While working with the Declarative Agent - Volunteering App, I successfully added a single plugin with API key authentication. I still need to update the solution to add additional plugin using OAuth as described in the sample https://github.com/pnp/copilot-pro-dev-samples/blob/main/samples/da-typespec-todo/actions.tsp#L65.
Key Issues:
Multiple Plugins with Auth:
- The toolkit currently supports one plugin with auth easily, but it’s unclear how to configure multiple plugins, each with its own authentication if using similar API Key (e.g., API key for OpenAI and Azure AI Search in the same app).
Documentation Gaps:
- The official samples and docs do not provide guidance for multi-auth scenarios
Please reach out or share your solution!
Conclusion
Using TypeSpec with M365 Agents Toolkit simplifies agent creation but comes with some limitations. Follow these steps and tips to avoid common pitfalls and streamline your workflow.
References
- Microsoft 365 Copilot Pro Dev Samples
- Copilot Dev camp - Extend M365 Copilot
- TypeSpec 101: Building Microsoft 365 Declarative Agents
- Integrating Microsoft Copilot Declarative Agents with Azure AI Search - A TypeSpec Approach
- TypeSpec
- M365 Agents Toolkit
- TypeSpec
- AzureOpenAI
- APIKeyAuth
- Azure AI Search
- Declarative Agents
- Agents
- M365 Copilot Extensibility
