Edit

Share via


Create an API-based message extension

Note

API-based message extensions only support search commands.

API-based message extensions are a Microsoft Teams app capability that integrates external APIs directly into Teams, enhancing your app's usability and offering a seamless user experience. API-based message extensions support search commands and can be used to fetch and display data from external services within Teams, streamlining workflows by reducing the need to switch between applications.

Before you get started, ensure that you meet the following requirements:


1. OpenAPI Description (OAD)

Ensure that you adhere to following guidelines for OpenAPI Description (OAD) document:

  • OpenAPI versions 2.0 and 3.0.x are supported.
  • JSON and YAML are the supported formats.
  • The request body, if present, must be application/Json.
  • Define an HTTPS protocol server URL for the servers.url property.
  • Only POST and GET HTTP methods are supported.
  • The OpenAPI Description document must have an operationId.
  • Only one required parameter without a default value is allowed.
  • A required parameter with a default value is considered optional.
  • Users must not enter a parameter for a header or cookie.
  • The operation must not have a required header or cookie parameters without default values.
  • Ensure that there are no remote references in the OpenAPI Description document.
  • Constructing arrays for the request isn’t supported; however, nested objects within a JSON request body are supported.
  • Teams doesn't support the oneOf, anyOf, allOf, and not (swagger.io) constructs.

The following code is an example of an OpenAPI Description document:

openapi: 3.0.1
info:
title: OpenTools Plugin
description: A plugin that allows the user to find the most appropriate AI tools for their use cases, with their pricing information.
version: 'v1'
servers:
- url: https://gptplugin.opentools.ai
paths:
/tools:
 get:
   operationId: searchTools
   summary: Search for AI Tools
   parameters:
     - in: query
       name: search
       required: true
       schema:
         type: string
       description: Used to search for AI tools by their category based on the keywords. For example, ?search="tool to create music" will give tools that can create music.
   responses:
     "200":
       description: OK
       content:
         application/json:
           schema:
             $ref: '#/components/schemas/searchToolsResponse'
     "400":
       description: Search Error
       content:
         application/json:
           schema:
             $ref: '#/components/schemas/searchToolsError'
components:
schemas:
 searchToolsResponse:
   required:
     - search
   type: object
   properties:
     tools:
       type: array
       items:
         type: object
         properties:
           name:
             type: string
             description: The name of the tool.
           opentools_url:
             type: string
             description: The URL to access the tool.
           main_summary:
             type: string
             description: A summary of what the tool is.
           pricing_summary:
             type: string
             description: A summary of the pricing of the tool.
           categories:
             type: array
             items:
               type: string
             description: The categories assigned to the tool.
           platforms:
             type: array
             items:
               type: string
             description: The platforms that this tool is available on.
       description: The list of AI tools.
 searchToolsError:
   type: object
   properties:
     message:
       type: string
       description: Message of the error.

For more information, see OpenAPI structure.


2. App manifest

Ensure that you adhere to following guidelines for app manifest:

  • Set the app manifest version to 1.17.

  • Set composeExtensions.composeExtensionType to apiBased.

  • Define composeExtensions.apiSpecificationFile as the relative path to the OpenAPI Description file within the folder. This links the app manifest to the API specification.

  • Define apiResponseRenderingTemplateFile as the relative path to the response rendering template. This specifies the location of the template used for rendering API responses.

  • Each command must have a link to the response rendering template. This connects each command to its corresponding response format.

  • The Commands.id property in the app manifest must match the operationId in the OpenAPI Description.

  • If a required parameter is without a default value, the command parameters.name in the app manifest must match the parameters.name in the OpenAPI Description document.

  • If there’s no required parameter, the command parameters.name in the app manifest must match the optional parameters.name in the OpenAPI Description.

  • Make sure that the parameters for each command match exactly with the names of the parameters defined for the operation in the OpenAPI spec.

  • A response rendering template must be defined per command, which is used to convert responses from an API.

  • Full description must not exceed 128 characters.

    {
    "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.17/MicrosoftTeams.schema.json",
    +  "manifestVersion": "1.17",
    "version": "1.0.0",
    "id": "04805b4b-xxxx-xxxx-xxxx-4dbc1cac8f89",
    "packageName": "com.microsoft.teams.extension",
    "developer": {
        "name": "Teams App, Inc.",
        "websiteUrl": "https://www.example.com",
        "privacyUrl": "https://www.example.com/termofuse",
        "termsOfUseUrl": "https://www.example.com/privacy"
    },
    "icons": {
        "color": "color.png",
        "outline": "outline.png"
    },
    "name": {
        "short": "AI tools",
        "full": "AI tools"
    },
    "description": {
        "short": "AI tools",
        "full": "AI tools"
    },
    "accentColor": "#FFFFFF",
    "composeExtensions": [
        {
    +      "composeExtensionType": "apiBased",
    +      "authorization": {
    +        "authType": "apiSecretServiceAuth ",
    +        "apiSecretServiceAuthConfiguration": {
    +            "apiSecretRegistrationId": "9xxxxxxx-7xxx-4xxx-bxxx-1xxxxxxxxxxx"
    +        }
    +      },
    +      "apiSpecificationFile": "aitools-openapi.yml",
           "commands": [
           {
              "id": "searchTools",
              "type": "query",
              "context": [
                 "compose",
                 "commandBox"
              ],
              "title": "search for AI tools",
              "description": "search for AI tools",
              "parameters": [
                 {
                 "name": "search",
                 "title": "search query",
                 "description": "e.g. search='tool to create music'"
                 }
              ],
    +          "apiResponseRenderingTemplateFile": "response-template.json"
           }
           ]
        }
    ],
    "validDomains": []
    }
    

Parameters

Name Description
composeExtensions.composeExtensionType Compose extension type. Update the value to apiBased.
composeExtensions.authorization Authorization related information for the API-based message extension
composeExtensions.authorization.authType Enum of possible authorization types. Supported values are none, apiSecretServiceAuth, and microsoftEntra.
composeExtensions.authorization.apiSecretServiceAuthConfiguration Object capturing details needed to do service auth. Applicable only when auth type is apiSecretServiceAuth.
composeExtensions.authorization.apiSecretServiceAuthConfiguration.apiSecretRegistrationId Registration ID returned when developer submits the API key through Developer Portal.
composeExtensions.apiSpecificationFile References an OpenAPI Description file in the app package. Include when type is apiBased.
composeExtensions.commands.id Unique ID that you assign to search command. The user request includes this ID. The ID must match the OperationId available in the OpenAPI Description.
composeExtensions.commands.context Array where the entry points for message extension is defined. The default values are compose and commandBox.
composeExtensions.commands.parameters Defines a static list of parameters for the command. The name must map to the parameters.name in the OpenAPI Description. If you're referencing a property in the request body schema, then the name must map to properties.name or query parameters.
composeExtensions.commands.apiResponseRenderingTemplateFile Template used to format the JSON response from developer’s API to Adaptive Card response. [Mandatory]

For more information, see composeExtensions.


3. Response rendering template
  • Define the schema reference URL in the $schema property to establish the structure of your template.
  • The supported values for responseLayout are list and grid, which determine how the response is visually presented.
  • A jsonPath is recommended for arrays or when the data for the Adaptive Card isn't the root object. For example, if your data is nested under productDetails, your JSON path would be productDetails.
  • Define jsonPath as the path to the relevant data or array in the API response. If the path points to an array, then each entry in the array binds with the Adaptive Card template and returns as a separate result. [Optional]
  • Get a sample response for validating the response rendering template. This serves as a test to ensure your template works as expected.
  • Use tools such as Fiddler or Postman to call the API and ensure that the request and the response are valid. This step is crucial for troubleshooting and confirming that your API is functioning correctly.
  • You can use the Adaptive Card Designer to bind the API response to the response rendering template and preview the Adaptive Card. Insert the template in the CARD PAYLOAD EDITOR and insert the sample response entry in the SAMPLE DATA EDITOR.

The following code is an example of a Response rendering template:

Response rendering template example
{
"version": "1.0",
"jsonPath": "repairs",
"responseLayout": "grid",
"responseCardTemplate": {
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "type": "AdaptiveCard",
  "version": "1.4",
  "body": [
    {
      "type": "Container",
      "items": [
        {
          "type": "ColumnSet",
          "columns": [
            {
              "type": "Column",
              "width": "stretch",
              "items": [
                {
                  "type": "TextBlock",
                  "text": "Title: ${if(title, title, 'N/A')}",
                  "wrap": true
                },
                {
                  "type": "TextBlock",
                  "text": "Description: ${if(description, description, 'N/A')}",
                  "wrap": true
                },
                {
                  "type": "TextBlock",
                  "text": "Assigned To: ${if(assignedTo, assignedTo, 'N/A')}",
                  "wrap": true
                },
                {
                  "type": "Image",
                  "url": "${image}",
                  "size": "Medium",
                  "$when": "${image != null}"
                }
              ]
            },
            {
              "type": "Column",
              "width": "auto",
              "items": [
                {
                  "type": "Image",
                  "url": "${if(image, image, '')}",
                  "size": "Medium"
                }
              ]
            }
          ]
        },
        {
          "type": "FactSet",
          "facts": [
            {
              "title": "Repair ID:",
              "value": "${if(id, id, 'N/A')}"
            },
            {
              "title": "Date:",
              "value": "${if(date, date, 'N/A')}"
            }
          ]
        }
      ]
    }
  ]
  },
  "previewCardTemplate": {
  "title": "Title: ${if(title, title, 'N/A')}",
  "subtitle": "Description: ${if(description, description, 'N/A')}",
  "text": "Assigned To: ${if(assignedTo, assignedTo, 'N/A')}",
  "image": {
    "url": "${image}",
    "$when": "${image != null}"
    }
  }
 }

Preview Card

Screenshot shows an example of compose extension displaying an array of preview cards when searching for a specific word. In this case, searching for 'a' in the 'SME test app' returns five cards showing 'Title', 'Description' (truncated) and 'AssignedTo' properties and values in each one.

Expanded Adaptive Card

Example of how the Adaptive Card looks like expanded once a user selects a preview card. The Adaptive Card shows the Title, the full Description, AssignedTo, RepairId, and Date values.

Parameters

Property Type Description Required
version string The schema version of the current response rendering template. Yes
jsonPath string The path to the relevant section in the results to which the responseCardTemplate and previewCardTemplate should be applied. If not set, the root object is treated as the relevant section. If the relevant section is an array, each entry is mapped to the responseCardTemplate and the previewCardTemplate. No
responseLayout responseLayoutType Specifies the layout of the results in the message extension flyout. The supported types are list and grid. Yes
responseCardTemplate adaptiveCardTemplate A template for creating an Adaptive Card from a result entry. Yes
previewCardTemplate previewCardTemplate A template for creating a preview card from a result entry. The resulting preview card is displayed in the message extension flyout menu. Yes

JSON path

The JSON path is optional but can be used for arrays or where the object to be used as the data for an Adaptive Card isn't the root object. The JSON path must follow the format defined by Newtonsoft. If the JSON path points to an array, then each entry in that array is bound with the Adaptive Card template and returns as separate results.

Example Let's say you have the below JSON for a list of products and you want to create a card result for each entry.

{
   "version": "1.0",
   "title": "All Products",
   "warehouse": {
      "products": [
        ...
      ]
   }
}

As you can see, the array of results is under "products", which is nested under "warehouse", so the JSON path would be "warehouse.products".

Use Adaptive Card Designer to preview an Adaptive Card by inserting the template into Card Payload Editor. Take a sample response entry from your array or for your object and insert it into Sample Data Editor. Ensure that the card renders properly and is to your liking.

Schema mapping

The properties in OpenAPI Description document are mapped to the Adaptive Card template as follows:

  • string, number, integer, boolean types are converted to a TextBlock.

    Example
    • Source Schema: string, number, integer, and boolean

       name:
         type: string
         example: doggie
      
    • Target Schema: Textblock

      {
      "type": "TextBlock",
      "text": "name: ${if(name, name, 'N/A')}",
      "wrap": true
      }
      
  • array: An array is converted to a container inside Adaptive Card.

    Example
    • Source schema: array

          type: array
                    items:
                    required:
                      - name
                    type: object
                      properties:
                      id:
                        type: integer
                      category:
                        type: object
                        properties:
                        name:
                          type: string
      
    • Target Schema: Container

          {
                    "type": "Container",
                    "$data": "${$root}",
                    "items": [
                      {
                        "type": "TextBlock",
                        "text": "id: ${if(id, id, 'N/A')}",
                        "wrap": true
                      },
                      {
                        "type": "TextBlock",
                        "text": "category.name: ${if(category.name, category.name, 'N/A')}",
                        "wrap": true
                      }
                    ]
                  }
      
      
  • object: An object is converted to a nested property in Adaptive Card.

    Example
    • Source Schema: object

      components:
        schemas:
          Pet:
              category:
                type: object
              properties:
                id:
                  type: integer
                name:
                  type: string
      
      
    • Target Schema: Nested property in an Adaptive Card

      {
        "type": "TextBlock",
        "text": "category.id: ${if(category.id, category.id, 'N/A')}",
        "wrap": true
      },
      {
        "type": "TextBlock",
        "text": "category.name: ${if(category.name, category.name, 'N/A')}",
        "wrap": true
      }
      
      
  • image: If a property is an image URL, then it converts to an Image element in the Adaptive Card.

    Example
    • Source schema: image

          image:
            type: string
            format: uri
            description: The URL of the image of the item to be repaired
      
      
    • Target Schema: "Image"

      {
            "type": "Image",
            "url": "${image}",
            "$when": "${image != null}"
          }
      
      

You can create an API-based message extension using Developer Portal for Teams, Microsoft 365 Agents Toolkit (previously known as Teams Toolkit) for Visual Studio Code, command line interface (CLI), or Visual Studio.

To create an API-based message extension using Developer Portal, follow these steps:

  1. Go to Developer Portal.

  2. Go to Apps.

  3. Select + New app.

  4. Enter a name of the app and select the Manifest version as Public developer preview (devPreview).

  5. Select Add.

    Screenshot shows the app name and the manifest version selected as Latest prerelease (devPreview) in Developer Portal.

  6. In the left pane, under Configure, update the following Basic information:

    1. Full name
    2. Short description
    3. Long description
    4. Developer or company name
    5. Website (must be a valid HTTPS URL)
    6. Privacy policy
    7. Terms of use
  7. Select Save.

  8. Select App features.

  9. Select Message extension.

    Screenshot shows the message extension option in Developer Portal.

  10. Under Message extension type, select API.

    1. If you get a disclaimer that reads Bot message extension is already in use by users. Would you like to change message extension type to API?, select Yes, change.
  11. Under OpenAPI spec, select Upload now.

    Screenshot shows the Upload now option in Developer Portal.

  12. Select the OpenAPI Description document in the JSON or YAML format and select Open.

  13. Select Save. A pop-up appears with the message API spec saved successfully.

  14. Select Got it.

    Screenshot shows an example of the API spec saved successfully message and Got it button.

Add commands

Note

Message extensions built from an API only support a single parameter.

You can add commands and parameters to your message extension, to add commands:

  1. Under Message extension type, select Add.

    Screenshot shows the add option to add commands in Developer Portal.

    An Add a command pop-up appears with a list of all the available APIs from the OpenAPI Description document.

  2. Select an API from the list and select Next.

    Screenshot shows the list of APIs from the OpenAPI Description Document in the Add a command pop-up window.

  3. Under Response template, select Upload now.

    Screenshot shows the Upload now option to add the Adaptive Card template in for the command.

    Note

    If you have more than one API, ensure that you upload the Adaptive Card response template for each API.

  4. Select the Adaptive Card response template file in JSON format and select Open.

    The following attributes are updated automatically from the Adaptive Card template:

    • Command Type
    • Command ID
    • Command title
    • Parameter name
    • Parameter description
  5. Under Details, update the Command description.

  6. If you want to launch a command using a trigger in Microsoft 365 Copilot, turn on the Automatically run this command when a user opens the extension toggle.

  7. Select Add. The command is added successfully.

    Screenshot shows the fields available in the command details page.

  8. Select Save.

  9. Under Authentication and authorization, select any of the following options:

    • No Authentication (not recommended)
    • API key
    • OAuth

An API-based message extension is created.

Screenshot shows the plugin for Microsoft 365 Copilot created in the app features page in Developer Portal.

To test your API-based message extension created in Developer Portal, you can use the following methods:

  • Preview in Teams: Open your message extension and select Preview in Teams in the upper-right corner. You're redirected to Teams, where you can add the app to Teams to preview the app.

  • Download app package: On the message extension page, select App package from the left pane and then, in the upper-left corner of the window, select Download app package. The app package is downloaded to your local machine in a .zip file. You can upload the app package to teams and test the message extension.

Multi parameters

Multi parameters allow API-based message extensions to have more than one input type for query commands. For example, you can search for anime by genre, rating, status, and date.

You can specify the input types, titles, descriptions, and required fields for the parameters in the manifest.

  • The isRequired property in the parameter field indicates if a parameter is mandatory for the query command.
  • The name property of the parameters field in the app manifest must match the id field in the OpenAPI Description document for the corresponding parameter.

Example

"composeExtensions": [
        {
            "composeExtensionType": "apiBased",
            "apiSpecificationFile": "apiSpecificationFiles/openapi.json",
            "commands": [
                {
                    "context": [
                        "compose"
                    ],
                    "type": "query",
                    "title": "Search Animes",
                    "id": "getAnimeSearch",
                    "parameters": [
                        {
                            "name": "q",
                            "title": "Search Query",
                            "description": "The search query",
                            "isRequired": true
                        },
                        {
                            "name": "type",
                            "inputType": "choiceset",
                            "title": "Type",
                            "description": "Available anime types",
                            "choices": [
                                {
                                    "title": "TV",
                                    "value": "tv"
                                },
                                {
                                    "title": "OVA",
                                    "value": "ova"
                                },
                                {
                                    "title": "Movie",
                                    "value": "movie"
                                },
                                {
                                    "title": "Special",
                                    "value": "special"
                                },
                                {
                                    "title": "ONA",
                                    "value": "ona"
                                },
                                {
                                    "title": "Music",
                                    "value": "music"
                                }
                            ]
                        },
                        {
                            "name": "status",
                            "inputType": "choiceset",
                            "title": "Status",
                            "description": "Available airing statuses",
                            "choices": [
                                {
                                    "title": "Airing",
                                    "value": "airing"
                                },
                                {
                                    "title": "Completed",
                                    "value": "complete"
                                },
                                {
                                    "title": "Upcoming",
                                    "value": "upcoming"
                                }
                            ]
                        },
                        {
                            "name": "rating",
                            "inputType": "choiceset",
                            "title": "Rating",
                            "description": "Available ratings",
                            "choices": [
                                {
                                    "title": "G",
                                    "value": "g"
                                },
                                {
                                    "title": "PG",
                                    "value": "pg"
                                },
                                {
                                    "title": "PG-13",
                                    "value": "pg13"
                                },
                                {
                                    "title": "R",
                                    "value": "r17"
                                },
                                {
                                    "title": "R+",
                                    "value": "r"
                                },
                                {
                                    "title": "Rx",
                                    "value": "rx"
                                }
                            ]
                        }
                    ],
                    "description": "Search animes",
                    "apiResponseRenderingTemplateFile": "response_json/getAnimeSearch.json"
                },
                {
                    "context": [
                        "compose"
                    ],
                    "type": "query",
                    "title": "Search mangas",
                    "id": "getMangaSearch",
                    "parameters": [
                        {
                            "name": "q",
                            "title": "Search Query",
                            "description": "The search query",
                            "isRequired": true
                        },
                        {
                            "name": "type",
                            "inputType": "choiceset",
                            "title": "Type",
                            "description": "Available manga types",
                            "choices": [
                                {
                                    "title": "Manga",
                                    "value": "manga"
                                },
                                {
                                    "title": "Novel",
                                    "value": "novel"
                                },
                                {
                                    "title": "Light Novel",
                                    "value": "lightnovel"
                                },
                                {
                                    "title": "One Shot",
                                    "value": "oneshot"
                                },
                                {
                                    "title": "Doujin",
                                    "value": "doujin"
                                },
                                {
                                    "title": "Manhwa",
                                    "value": "manhwa"
                                },
                                {
                                    "title": "Manhua",
                                    "value": "manhua"
                                }
                            ]
                        },
                        {
                            "name": "status",
                            "inputType": "choiceset",
                            "title": "Status",
                            "description": "Available manga statuses",
                            "choices": [
                                {
                                    "title": "Publishing",
                                    "value": "publishing"
                                },
                                {
                                    "title": "Complete",
                                    "value": "complete"
                                },
                                {
                                    "title": "Hiatus",
                                    "value": "hiatus"
                                },
                                {
                                    "title": "Discontinued",
                                    "value": "discontinued"
                                },
                                {
                                    "title": "Upcoming",
                                    "value": "upcoming"
                                }
                            ]
                        },
                        {
                            "name": "start_date",
                            "title": "Start Date",
                            "description": "Start date of the manga",
                            "inputType": "date"
                        },
                        {
                            "name": "end_date",
                            "title": "End Date",
                            "description": "End date of the manga",
                            "inputType": "date"
                        }
                    ],

Step-by-step guides

To build an API-based message extension, follow these step-by-step guides:

Tutorial: Build API-based message extension

Note

API-based message extensions only support search commands.

Message extensions built using API (API-based) significantly enhance the functionality of your Teams apps by allowing them to interact with external services. API-based message extensions can help streamline workflows by reducing the need to switch between different applications.

You can use API-based message extensions to integrate external services that are commonly used in the business workflow. For example, a business that frequently uses a CRM system for customer management could use a message extension to fetch and display customer data directly from Teams. This app helps save time and improves efficiency by reducing the need to switch between different applications. This feature is supported on all platforms where Teams is available, including desktop, web, and mobile.

Prerequisites

Here's a list of tools you need for building and deploying your apps.

Install For using...
Microsoft Teams Microsoft Teams to collaborate with everyone you work with through apps for chat, meetings, or call - all in one place.
Microsoft Edge (recommended) or Google Chrome A browser with developer tools.
Visual Studio Code JavaScript, TypeScript, or SharePoint Framework (SPFx) build environments. Use version 1.55 or later.
Microsoft 365 developer account Access to Teams account with the appropriate permissions to install an app.
Azure account Access to Azure resources.
OpenAPI Description (OAD) document A document that describes the capabilities of your API. For more information, see OpenAPI Description.

Set up your Teams development tenant

A tenant is like a space, or a container for your organization in Teams, where you chat, share files, and run meetings. This space is also where your upload and test your custom app. Let's verify if you're ready to develop with the tenant.

Check for custom app upload option

After creating the app, you must load your app in Teams without distributing it. This process is known as custom app upload. Sign in to your Microsoft 365 account to view this option.

Note

Custom app upload is necessary for previewing and testing apps in Teams local environment. If it isn't enabled, you can't preview and test your app in Teams local environment.

Do you already have a tenant, and do you have the admin access? Let's check if you really do!

Verify if you can upload a custom app in Teams:

  1. In the Teams client, select the Apps icon.

  2. Select Manage your apps.

  3. Select Upload an app.

  4. Look for the option to Upload a custom app. If you see the option, custom app upload is enabled.

    Screenshot shows the bot home.

Note

Contact your Teams administrator, if you don't find the option to upload a custom app.

Create a free Teams developer tenant (optional)

If you don't have a Teams developer account, you can get it free. Join the Microsoft 365 developer program!

  1. Go to the Microsoft 365 developer program.

  2. Select Join Now and follow the onscreen instructions.

  3. In the welcome screen, select Set up E5 subscription.

  4. Set up your administrator account. After you finish, the following screen appears.

    Screenshot shows the Microsoft 365 Developer Program.

  5. Sign in to Teams using the administrator account you just set up. Verify that you have the Upload a custom app option in Teams.

Get a free Azure account

If you want to host your app or access resources in Azure, you must have an Azure subscription. Create a free account before you begin.

You have all the tools to set up your account. Next, let's set up your development environment and start building! Select the app you want to build first.

Create OpenAPI Description document

OpenAPI Description

OpenAPI Description (OAD) is the industry-standard specification that outlines how OpenAPI files are structured and outlined. It's a language-agnostic, human-readable format for describing APIs. It's easy for both humans and machines to read and write. The schema is machine-readable and represented in either YAML or JSON.

To interact with the APIs, an OpenAPI Description document is necessary. The OpenAPI Description document must meet the following criteria:

  • The auth property must not be specified.
  • JSON and YAML are the supported formats.
  • OpenAPI Versions 2.0 and 3.0.x are supported.
  • Teams doesn't support the oneOf, anyOf, allOf, and not (swagger.io) constructs.
  • Constructing arrays for the request isn't supported, however, nested objects within a JSON request body are supported.
  • The request body, if present, must be application/Json to ensure compatibility with a wide range of APIs.
  • Define an HTTPS protocol server URL for the servers.url property.
  • Only single parameter search is supported.
  • Only one required parameter without a default value is allowed.
  • Only POST and GET HTTP methods are supported.
  • The OpenAPI Description document must have an operationId.
  • The operation mustn't require Header or Cookie parameters without default values.
  • A command must have exactly one parameter.
  • Ensure that there are no remote references in the OpenAPI Description document.
  • A required parameter with a default value is considered optional.

We used the following OpenAPI Description as an example for this tutorial:

OpenAPI Description
openapi: 3.0.1
info:
  title: OpenTools Plugin
  description: A plugin that allows the user to find the most appropriate AI tools for their use cases, with their pricing information.
  version: 'v1'
servers:
  - url: https://gptplugin.opentools.ai
paths:
  /tools:
    get:
      operationId: searchTools
      summary: Search for AI Tools
      parameters:
        - in: query
          name: search
          required: true
          schema:
            type: string
          description: Used to search for AI tools by their category based on the keywords. For example, a search for "tool to create music" provides a list of tools that can create music.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/searchToolsResponse'
        "400":
          description: Search Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/searchToolsError'
components:
  schemas:
    searchToolsResponse:
      required:
        - search
      type: object
      properties:
        tools:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
                description: The name of the tool.
              opentools_url:
                type: string
                description: The URL to access the tool.
              main_summary:
                type: string
                description: A summary of what the tool is.
              pricing_summary:
                type: string
                description: A summary of the pricing of the tool.
              categories:
                type: array
                items:
                  type: string
                description: The categories assigned to the tool.
              platforms:
                type: array
                items:
                  type: string
                description: The platforms that this tool is available on.
          description: The list of AI tools.
    searchToolsError:
      type: object
      properties:
        message:
          type: string
          description: Message of the error.
      

Note

Ensure that the required: true property is available for only one parameter. If there are more than one required parameters, you can update the required property to required: false for the other parameters.

You can validate if the OpenAPI Description document is valid. To verify, follow these steps:

  1. Go to Swagger/OpenAPI validator and validate the OpenAPI Description document.

  2. Save the OpenAPI Description document.

  3. Go to Swagger Editor.

  4. In the left pane, paste the OpenAPI Description in the editor.

  5. In the right pane, select GET.

  6. Select Try it out.

  7. Enter the values for the search parameter as Tool to create music.

  8. Select Execute. The swagger editor displays a response with a list of products.

    Screenshots shows the parameters, its values and the **EXECUTE** option in the swagger editor.

  9. Go to Server response > Response Body.

  10. Under products, copy the first product from the list and save it for future reference.

    Screenshots shows the highlighted product that is selected from the response body.

Create response rendering template

An OpenAPI Description document requires a response rendering template for the app to respond to the GET or POST requests. The response rendering template consists of an Adaptive Card template, Preview card template, and metadata.

Adaptive Card template

To create an Adaptive Card template, follow these steps:

  1. Go to ChatGPT and ask the following query in the message compose area:

    Create an Adaptive Card Template that binds to the following response:
        "categories": [
            "Music Generation",
            "AI Detection"
        ],
        "chatbot_short_url": "https://goto.opentools.ai/c/ai-music-generator",
        "main_summary": "AI Music Generator is an AI-powered music composing tool that allows users to create original and personalized music for various purposes. It can generate melodies, harmonies, and rhythms tailored to specific needs and preferences, with customization options such as genre, mood, length, and instrumentation. The tool is designed for creative individuals, from beginners to professionals, and can produce high-quality music in seconds. Every generated piece of music is royalty-free and can be used instantly, with no limitations on beat creation. With advanced AI technology, AI Music Generator makes music production accessible to everyone.",
        "name": "AI Music Generator",
        "opentools_url": "https://goto.opentools.ai/ai-music-generator",
        "platforms": [
            "Web",
            "App",
            "API"
        ]
    
  2. Select Send message.

  3. ChatGPT generates a response with an Adaptive Card template that binds to the sample data. Save the Adaptive Card template for future reference.

Following is an example of the Adaptive Card template:

Adaptive Card template
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.4",
"body": [
    {
    "type": "TextBlock",
    "text": "AI Music Generator",
    "weight": "Bolder",
    "size": "Large"
    },
    {
    "type": "TextBlock",
    "text": "Categories",
    "size": "Medium"
    },
    {
    "type": "TextBlock",
     "text": "Music Generation, AI Detection",
     "wrap": true
    },
    {
    "type": "TextBlock",
    "text": "Description",
    "size": "Medium"
    },
    {
    "type": "TextBlock",
    "text": "AI Music Generator is an AI-powered music composing tool that allows users to create original and personalized music for various purposes. It can generate melodies, harmonies, and rhythms tailored to specific needs and preferences, with customization options such as genre, mood, length, and instrumentation. The tool is designed for creative individuals, from beginners to professionals, and can produce high-quality music in seconds. Every generated piece of music is royalty-free and can be used instantly, with no limitations on beat creation. AI Music Generator is powered by advanced AI technology, and it makes music production accessible to everyone.",
    "wrap": true
    },
    {
    "type": "TextBlock",
    "text": "Platform",
    "size": "Medium"
    },
    {
    "type": "TextBlock",
    "text": "Web, App, API",
    "wrap": true
    }
],
"actions": [
    {
    "type": "Action.OpenUrl",
    "title": "Learn More",
    "url": "https://goto.opentools.ai/ai-music-generator"
    },
    {
    "type": "Action.OpenUrl",
    "title": "Try It",
    "url": "https://goto.opentools.ai/c/ai-music-generator"
    }
]
}

  1. To verify if the Adaptive Card generated binds to the sample data, follow these steps:
    1. Go to Adaptive Card Designer.

    2. Go to Select host app, and then select Microsoft Teams from the dropdown.

    3. Go to CARD PAYLOAD EDITOR and paste the Adaptive Card template code.

    4. Go to SAMPLE DATA EDITOR and paste the GET API response that you saved earlier.

      Screenshots shows the Adaptive Card designer with the Adaptive Card template and the sample data.

    5. Select Preview mode. The Adaptive Card designer displays an Adaptive Card with the data that binds the response to the template.

      Screenshot shows the Adaptive Card designer with the Adaptive Card template and the sample data.

Create a preview card template

The preview card template can contain a title, subtitle and image properties. If the API response doesn't have an image, you can remove the image property.

Following is an example of a preview card template:

Preview card template
   "previewCardTemplate": {
        "title": "${if(name, name, 'N/A')}",
        "subtitle": "$${if(price, price, 'N/A')}"
    } 

Create an if condition for the title and subtitle, where:

  • If name exists, the bot uses the name.
  • If name doesn't exist, the bot uses NA.

For example, "title": "Name: ${if(name, name, 'N/A')}". Save the preview card template for future reference.

Response rendering template

The response rendering template must conform to the schema hosted at https://developer.microsoft.com/json-schemas/teams/v1.20/MicrosoftTeams.ResponseRenderingTemplate.schema.json.

To create a response rendering template, follow these steps:

  1. Create a JSON file and add the following code to the file:

    { 
      "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.20/MicrosoftTeams.ResponseRenderingTemplate.schema.json", 
      "version": "1.0", 
      "jsonPath": "", 
      "responseLayout": "", 
      "responseCardTemplate": { 
     },
     "previewCardTemplate": {
         }
     }
    
  2. Update the properties in the response rendering template as follows:

    1. "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.20/MicrosoftTeams.ResponseRenderingTemplate.schema.json"

    2. "version": "1.0"

      version is the version of the rendering template to use

    3. "jsonPath": "tools"

      jsonPath is the path to one or more results in the response JSON response. Add the jsonPath to the relevant data/array from the product list in the API response. In this case, the jsonPath is tools. For more information on how to determiner the JSON path, see Querying JSON with JSON path.

    4. "responseLayout": "list"

      responseLayout specifies the layout of the attachments. Used for responses of type result. Supported types are list and grid. If the response body contains an object with multiple elements like text, title, and image, then the response layout must be set to list. If the API response contains only images or thumbnails, then the response layout must be set to grid.

    5. "responseCardTemplate": Paste the Adaptive Card template code that you saved earlier.

      responseCardTemplate is an Adaptive Card template to map the JSON response to an Adaptive Card.

    6. "previewCardTemplate": Paste the preview card template code that you saved earlier.

      previewCardTemplate is a preview card template is used to show a preview of results in the message extension flyout.

  3. Save the response rendering template in the same folder you saved the OpenAPI Description document.

The following code is an example of a Response rendering template:

Response rendering template
{
    "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.20/MicrosoftTeams.ResponseRenderingTemplate.schema.json",
    "version": "1.0",
    "jsonPath": "tools",
    "responseLayout": "list",
    "responseCardTemplate": {
        "type": "AdaptiveCard",
        "version": "1.4",
        "body": [
            {
            "type": "TextBlock",
            "text": "AI Music Generator",
            "weight": "Bolder",
            "size": "Large"
            },
            {
            "type": "TextBlock",
            "text": "Categories",
            "size": "Medium"
            },
            {
            "type": "TextBlock",
            "text": "Music Generation, AI Detection",
            "wrap": true
            },
            {
            "type": "TextBlock",
            "text": "Description",
            "size": "Medium"
            },
            {
            "type": "TextBlock",
            "text": "AI Music Generator is an AI-powered music composing tool that allows users to create original and personalized music for various purposes. It can generate melodies, harmonies, and rhythms tailored to specific needs and preferences, with customization options such as genre, mood, length, and instrumentation. The tool is designed for creative individuals, from beginners to professionals, and can produce high-quality music in seconds. Every generated piece of music is royalty-free and can be used instantly, with no limitations on beat creation. With advanced AI technology, AI Music Generator makes music production accessible to everyone.",
            "wrap": true
            },
            {
            "type": "TextBlock",
            "text": "Platform",
            "size": "Medium"
            },
            {
            "type": "TextBlock",
            "text": "Web, App, API",
            "wrap": true
            }
        ],
        "actions": [
            {
            "type": "Action.OpenUrl",
            "title": "Learn More",
            "url": "https://goto.opentools.ai/ai-music-generator"
            },
            {
            "type": "Action.OpenUrl",
            "title": "Try It",
            "url": "https://goto.opentools.ai/c/ai-music-generator"
            }
        ]
    },
    "previewCardTemplate": {
        "title": "${if(name, name, 'N/A')}",
        "subtitle": "$${if(price, price, 'N/A')}"
    } 
}

Create app manifest

Now, you need to create an app manifest (previously called Teams app manifest). The app manifest describes how your app integrates into the Microsoft Teams product.

Create a Teams app manifest

To create the manifest, follow these steps:

  1. Create a new JSON file. Your app manifest must conform to the 1.20 version of the schema defined in App manifest schema.

  2. Add the following code to the JSON file:

    App manifest
    {
     "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.20/MicrosoftTeams.schema.json",
     "manifestVersion": "1.20",
     "version": "1.0.3",
     "id": "<<YOUR-MICROSOFT-APP-ID>>",
     "packageName": "com.microsoft.teams.extension",
     "developer": {
         "name": "Teams App, Inc.",
         "websiteUrl": "https://www.example.com",
         "privacyUrl": "https://www.example.com/termofuse",
         "termsOfUseUrl": "https://www.example.com/privacy"
     },
     "icons": {
         "color": "color.png",
         "outline": "outline.png"
     },
     "name": {
         "short": "Search ME API",
         "full": "Search ME API full"
     },
     "description": {
         "short": "product app for testing API Message Extensions",
         "full": "product app for testing API Message Extensions"
     },
     "accentColor": "#FFFFFF",
     "composeExtensions": [
         {
             "composeExtensionType": "",
             "apiSpecificationFile": "",
             "commands": [
                 {
                     "context": [
                         "compose"
                     ],
                     "type": "query",
                     "title": "API for fetching Klarna.",
                     "id": "",
                     "parameters": [
                         {
                             "name": "",
                             "title": "",
                             "description": ""
                         }
                     ],
                     "description": "",
                     "apiResponseRenderingTemplateFile": ""
                 }
             ]
         }
     ],
     "permissions": [
         "identity",
         "messageTeamMembers"
     ],
     "validDomains": []
    }
    
  3. Update the app manifest properties as follows:

    • Replace <<YOUR-MICROSOFT-APP-ID>> with bot's Microsoft App ID.
    • Update the value for composeExtensionType to apiBased.
    • Update the value for apiSpecificationFile to the path of your OpenAPI Description file.
    • Update the value for commands.id to searchTools.
    • Update the value for commands.title to Search for AI Tools.
    • Update the value for commands.description to Search for AI Tools.
    • Update the value for parameters.name to search. If there are no parameters, then the values must be query parameters or properties.name if referencing a property in the request body schema.
    • Update the apiResponseRenderingTemplateFile to the path of your response rendering template file.
    • Update the value for validDomains to the service URL endpoint defined in the OpenAPI Description file.
  4. Save the Teams app manifest in the same folder you saved the OpenAPI Description document and the response rendering template.

    • You need a color image and outline image. These images should be included in the folder and referenced in your Teams app manifest.
    • Zip up the contents of the folder. The zip file must include the following files:
      • OpenAPI Description document
      • Response rendering template
      • App manifest
      • Color icon
      • Outline icon

Upload a custom app to Teams

Sign into Teams test environment to test your app in Teams. To upload a custom app in Teams, follow these steps:

  1. Go to Microsoft Teams and sign in using your test tenant credentials.

  2. Go to Apps > Manage your app > Upload an app.

  3. Select Upload a customized app.

  4. Select the zip file created and select Open.

  5. Select Add.

    Screenshot of message extension app with the Add option highlighted.

  6. Select Open.

    Screenshot of message extension app with the Open option highlighted.

  7. Go to a chat, then select + from the message compose area, and search for your app.

  8. Select the app and make a search query.

    Screenshot shows that from the plus icon in the chat menu, users can invoke the message extension app that is displayed in the flyout menu.

  9. The app responds with an Adaptive Card in the chat window.

  10. Select Send.

    Screenshot shows the Adaptive Card with the search results in the chat message in Teams.

Congratulations! You did it! You learned to create an API-based message extension using OpenAPI Description document.

See also

Authentication for API-based message extensions