Power Apps,  Power Automate

Exploring OpenAPI Extensions – Dynamic Schema

This time of the year for me is about exploring things that are on my “tinker-later” list and tackling them with a festive zest and playful spirit in time for the holiday season! 😄🎄 We are going to explore one of the OpenAPI extensions, Dynamic Schema (x-ms-dynamic-schema), to extend the functionality of a custom connector.

To describe what this is all about, let’s start with the requirement. In our simple scenario, we have meetings and a set of attendees for those meetings, with each attendee assigned a meeting role. Currently, there is a Meetings API that exposes this data in a structure as shown in the payload below:

{
  "id": 2,
  "date": "2021-12-23T00:00:00",
  "name": "Delivery meeting",
  "type": 1,
  "attendees": [
    {
      "id": 1,
      "fullname": "Santa",
      "role": { "id": 1, "name": "CEO", "displayName": "Chief Executive Officer" }
    },
    ...
  ]
}

A new requirement came up and this time we need to return the same data in a flattened structure with individual roles as root properties of the payload without the attendees array. Here is an example of what the new payload should look like:

{
  "id": 2,
  "date": "2021-12-23T00:00:00",
  "name": "Delivery meeting",
  "ceo": {
    "attendeeFullname": "Santa",
    "attendeeId": 1,
    "roleFullname": "Chief Executive Officer",
    "roleId": 1
  },
  ...
}

With that, here is the list of objectives and what we are going to cover in this post:

  • Understand the usage of dynamic schemas and define them in the OpenAPI (Swagger) specification to create our custom connector
  • Look at different ways to generate dynamic schemas for dynamic data in C#
  • Easily access the dynamic properties as dynamic content in Power Automate flow actions
  • Capture and display the dynamic content using the experimental Canvas App feature called Dynamic Schema

 

What is Dynamic Schema?

To understand the use of Dynamic Schema, we must first revisit the definition of the OpenApi specification:

Swagger (OpenAPI) is a language-agnostic specification for describing REST APIs. It allows both computers and humans to understand the capabilities of a REST API without direct access to the source code

ASP.NET Core web API documentation with Swagger / OpenAPI

In a nutshell, it’s like a blueprint for API implementations. It describes the API, including the available endpoints, the HTTP verb operations on each endpoint, as well as the input and output parameters for each operation, and much more! Something else it defines which is of great interest to us is the JSON schema for the expected data types used by the API. This schema defines the format of each property of the data returned in the operation response and can also be used to define the schema of the body payload of operations.

To see what this looks like, here is the OpenAPI specification for the v1 endpoint for the Meetings API:

swagger: "2.0"
info: { title: Sandbox Sample Connector, version: v1 }
host: sandboxsampleconnector.azurewebsites.net
basePath: /
schemes: [https]
consumes: []
produces: []
paths:
  /api/v1/meetings/{meetingId}:
    get:
      tags: [MeetingsV1]
      produces: [application/json]
      parameters:
        - {
            in: path,
            name: meetingId,
            required: true,
            type: integer,
            format: int32,
            description: Enter meeting ID,
            x-ms-summary: Meeting Id,
          }
      responses:
        "200":
          description: Success
          schema:
            type: object
            title: Meeting
            properties:
              id: { format: int32, type: integer, title: Meeting Id }
              date: { format: date-time, type: string, title: Meeting Date }
              name: { type: string, title: Meeting Name }
              type:
                title: Meeting Type
                format: int32
                enum: [0, 1, 2]
                type: integer
              attendees:
                type: array
                title: Attendees
                items:
                  type: object
                  properties:
                    id: { format: int32, type: integer, title: Attendee Id }
                    fullname: { type: string, title: Attendee Full Name }
                    role:
                      type: object
                      title: Attendee Meeting Role
                      properties:
                        id:
                          {
                            format: int32,
                            type: integer,
                            title: Meeting Role Id,
                          }
                        name: { type: string, title: Meeting Role Name }
                        displayName:
                          { type: string, title: Meeting Role Display Name }
      summary: Get Meeting V1
      operationId: GetMeetingV1
definitions: {}
parameters: {}
responses: {}
securityDefinitions: {}
security: []
tags: []
Note: I purposely inlined the schema definition for readability and understanding. We could have used the schema: {$ref: '#/definitions/Meeting'} and defined the Meeting schema under definitions: {...} for reusability. 

By explicitly defining the schema this way, we are basically stating that this is what to expect when using the API. You can see how the example payload of the v1 Meetings API endpoint is described by this schema definition. But what if we don’t know what this schema would look like? This is where the dynamic schema comes into play 😎. This is what the official Microsoft documentation has to say:

The dynamic schema indicates that the schema for the current parameter or response is dynamic. This object invokes an operation that’s defined by the value in this field, dynamically discovers the schema, and displays the appropriate user interface (UI) for collecting user input or shows the available fields

Use dynamic schema

Here is the OpenAPI specification for the v2 endpoint for the Meetings API using the dynamic schema definition:

swagger: '2.0'
info: {title: Sandbox Sample Connector, version: v2}
host: sandboxsampleconnector.azurewebsites.net
basePath: /
schemes: [https]
consumes: []
produces: []
paths:
  /api/v2/meetings/{meetingId}:
    get:
      tags: [MeetingsV2]
      parameters:
      - {in: path, name: meetingId, required: true, type: integer, format: int32,
        description: Enter meeting ID, x-ms-summary: Meeting ID}
      responses:
        '200':
          description: Success
          schema:
            type: object
            x-ms-dynamic-schema:
              operationId: GetMeetingSchema
              parameters:
                meetingId: {parameter: meetingId}
      summary: Get Meeting
      operationId: GetMeeting
  /api/v2/meetings/{meetingId}/schema:
    get:
      tags: [MeetingsV2]
      parameters:
      - {in: path, name: meetingId, required: true, type: integer, format: int32,
        description: Enter meeting ID, x-ms-summary: Meeting ID}
      responses:
        '200':
          description: Success
          schema: {type: object}
      summary: Get Meeting Schema
      operationId: GetMeetingSchema
      x-ms-visibility: internal
definitions: {}
parameters: {}
responses: {}
securityDefinitions: {}
security: []
tags: []
Note: The OpenAPI specification caters for the most common cases, but it can also be extended to meet specific requirements of the authors or external vendors utilising the OpenAPI for their own tooling and APIs, etc. These extension properties are always prefixed with "x-<unique name>", where Microsoft OpenAPI extensions are prefixed with "x-ms-<unique name>". A list of Microsoft OpenAPI extensions to use with building custom connectors for Power Automate and Logic Apps can be found here. 

As you can see, we are using the x-ms-dynamic-schema OpenAPI extension to indicate that the schema for the operation response will be dynamically retrieved by calling the GetMeetingSchema operation. This operation has x-ms-visibility set to internal, to make sure it is hidden from the users and only available for internal use. What is defined here is pretty minimal and is just an instruction, the majority of the work of creating the dynamic content and schema is done by the API. Let’s have a look at that next 👇🏻.

 

Generating the Dynamic Content and Dynamic Schema using C#

We are going to cover 3 different scenarios. We are going to generate dynamic schemas for:

  1. An untyped object (a.k.a anonymous type)
  2. A typed object
  3. A JSON schema parsed and validated against an untyped object

The assumption here is that the service that returns the meeting object with attendees as an array property cannot be modified, therefore, the denormalisation of this meeting object to cater for the flattened structure will be implemented at the API layer.

To generate the schema in C#, we will be using the following NuGet packages:

  • Newtonsoft.Json
  • Newtonsoft.Json.Schema
  • NJsonSchema
  • NJsonSchema.CodeGeneration

Also, for my implementation of the API, I’m using Swashbuckle.AspNetCore NuGet package to generate the Swagger (OpenAPI) definition which I won’t cover here.

 

#1. Dynamic schema for an untyped object

Scenario: A meeting has unique meeting roles and the number is unpredictable. Each unique role must be defined as a root property of the meeting object returned.

// Return untyped meeting object
// GET /api/v2/meetings/{meetingId}
// e.g. /api/v2/meetings/1

private IActionResult GetUntypedMeeting(Meeting meeting)
{
  var untypedMeeting = MapUntypedMeeting(meeting);
  return Content(untypedMeeting.ToString(), "application/json", Encoding.UTF8);
}

private JObject MapUntypedMeeting(Meeting meeting)
{
  var mappedMeeting = new JObject
  {
    ["id"] = meeting.Id,
    ["name"] = meeting.Name,
    ["date"] = meeting.Date
  };

  foreach (var attendee in meeting.Attendees)
  {
    mappedMeeting[attendee.Role.Name.ToLower()] = new JObject
    {
      ["attendeeId"] = attendee.Id,
      ["attendeeFullname"] = attendee.Fullname,
      ["roleName"] = attendee.Role.DisplayName
    };
  }
  return mappedMeeting;
}

// Return schema for untyped meeting object
// GET /api/v2/meetings/{meetingId}/schema
// e.g. /api/v2/meetings/1/schema

private IActionResult GetUntypedMeetingSchema(Meeting meeting)
{
  var schema = new JSchema
  {
    Type = JSchemaType.Object,
    Properties =
    {
      { 
        "id", 
        new JSchema { Type = JSchemaType.String, Title = "Meeting Id" } 
      },
      { 
        "name", 
        new JSchema { Type = JSchemaType.String, Title = "Meeting Name" } 
      },
      { 
        "date", 
        new JSchema { Type = JSchemaType.String, Title = "Meeting Date", Format = "date" } 
      }
    }
  };

  foreach (var attendee in meeting.Attendees)
  {
    var schemaRole = new JSchema
    {
      Type = JSchemaType.Object,
      Description = $"The role {attendee.Role.DisplayName}",
      Title = attendee.Role.Name,
      Properties =
      {
        { 
          "attendeeId", 
          new JSchema { Type = JSchemaType.String, Title = "Attendee Id", Description = $"The {attendee.Role.Name} role attendee ID" } 
        },
        { 
          "attendeeFullname", 
          new JSchema { Type = JSchemaType.String, Title = "Attendee Name", Description = $"The {attendee.Role.Name} role attendee name" } 
        },
        { 
          "roleName", 
          new JSchema { Type = JSchemaType.String, Title = "Role Name", Description = $"The {attendee.Role.Name} role display name" } 
        },
      }
    };
    schema.Properties.Add(attendee.Role.Name.ToLower(), schemaRole);
  }
  return Content(schema.ToString(), "application/json", Encoding.UTF8);
}

The above meeting schema is manually crafted in code using the JSchema class. I was curious to know if there is a way to automatically generate this schema from the object itself. The simple answer is yes, by using the NJsonSchema.CodeGeneration.SampleJsonSchemaGenerator. However, if the generator detects a reusable schema then it will reference it using the $ref field and define the schema object at the definitions node. Unfortunately, Power Automate doesn’t detect the dynamic schema when structured this way. Every schema must be defined in-line and not referenced when specifying the dynamic schema. Currently, there are no generator settings available in NJsonSchema NuGet to tell it to ignore the reference handling. So here comes the (hacky) workaround, we can achieve this without writing much code by parsing the schema twice but the second time with the Newtonsoft.Json.Schema:

private IActionResult GetUntypedMeetingSchemaAlternative(Meeting meeting)
{
  var untypedMeeting = MapUntypedMeeting(meeting);
  var generator = new SampleJsonSchemaGenerator();
  var firstPassSchema = generator.Generate(untypedMeeting.ToString());

  var secondPassSchema = JSchema.Parse(firstPassSchema.ToJson());
  var schemaJson = secondPassSchema.ToString(new JSchemaWriterSettings
  {
    ReferenceHandling = JSchemaWriterReferenceHandling.Never
  });

  return Content(schemaJson, "application/json", Encoding.UTF8);
}
Note: This is very experimental as I am also learning 😅. If you are reading this and know of an elegant way, please let me know!
// Output - Meeting Payload
{
  "id": 1,
  "name": "Pre-flight meeting",
  "date": "2021-12-23T00:00:00",
  "frr": { "attendeeId": 1, "attendeeFullname": "Dasher", "roleName": "Flight Rear Right"  },
  "frl": { "attendeeId": 2, "attendeeFullname": "Dancer", "roleName": "Flight Rear Left"  },
  "fmr": { "attendeeId": 3, "attendeeFullname": "Prancer", "roleName": "Flight Middle Right" },
  "fml": { "attendeeId": 4, "attendeeFullname": "Vixen", "roleName": "Flight Middle Left" },
  "ffr": { "attendeeId": 5, "attendeeFullname": "Comet", "roleName": "Flight Front Right" },
  "ffl": { "attendeeId": 6, "attendeeFullname": "Cupid", "roleName": "Flight Front Left"  },
  "fcpr": { "attendeeId": 7, "attendeeFullname": "Donner", "roleName": "Flight Co-Pilot Right" },
  "fcpl": { "attendeeId": 8, "attendeeFullname": "Blitzen", "roleName": "Flight Co-Pilot Left" },
  "fp": { "attendeeId": 9, "attendeeFullname": "Rudolph", "roleName": "Flight Pilot" },
  ...[more]
}

// Output - Meeting Schema
{
  "type": "object",
  "properties": {
    "id": { "title": "Meeting Id", "type": "string" },
    "name": { "title": "Meeting Name", "type": "string" },
    "date": { "title": "Meeting Date", "type": "string", "format": "date" },
    "frr": {
      "title": "FRR",
      "description": "The role Flight Rear Right",
      "type": "object",
      "properties": {
        "attendeeId": { 
          "title": "Attendee Id", 
          "description": "The FRR role attendee ID", 
          "type": "string" 
        },
        "attendeeFullname": { 
          "title": "Attendee Name", 
          "description": "The FRR role attendee name", 
          "type": "string" },
        "roleName": { 
          "title": "Role Name", 
          "description": "The FRR role display name", 
          "type": "string" 
        }
      }
    },
    "frl": { /* same as above */ },
    "fmr": { /* same as above */ },
    "fml": { /* same as above */ },
    "ffr": { /* same as above */ },
    "ffl": { /* same as above */ },
    "fcpr": { /* same as above */ },
    "fcpl": { /* same as above */ },
    "fp": { /* same as above */ },
    ...[more]
  }
}

 

#2. Dynamic schema for a typed object

Scenario: A meeting has a set of predictable meeting roles to assign to attendees. These meetings can be templated and rarely change. Each predetermined role must be defined as a root property of the meeting object returned.

// Return typed meeting object
// GET /api/v2/meetings/{meetingId}
// e.g. /api/v2/meetings/2

private IActionResult GetTypedMeeting(Meeting meeting)
{
  var typedMeeting = MapTypedMeeting(meeting);
  return Ok(typedMeeting);
}

private ExecutiveMeeting MapTypedMeeting(Meeting meeting)
{
    var mappedMeeting = new ExecutiveMeeting
    {
      Id = meeting.Id,
      Name = meeting.Name,
      Date = meeting.Date
    };

    foreach (var attendee in meeting.Attendees)
    {
      var assignment = new MeetingAssignment
      {
        AttendeeFullname = attendee.Fullname,
        AttendeeId = attendee.Id,
        RoleFullname = attendee.Role.DisplayName,
        RoleId = attendee.Role.Id
      };

      switch (attendee.Role.Name)
      {
        case "CEO":
          mappedMeeting.CEO = assignment;
          break;
        case "CDO":
          mappedMeeting.CDO = assignment;
          break;
        case "CFO":
          mappedMeeting.CFO = assignment;
          break;
        case "COO":
          mappedMeeting.COO = assignment;
          break;
        case "CPO":
          mappedMeeting.CPO = assignment;
          break;
      }
    }
    return mappedMeeting;
}

public class ExecutiveMeeting
{
  [DisplayName("Meeting Id")]
  public int Id { get; set; }

  [DisplayName("Meeting Date")]
  public DateTime Date { get; set; }

  [DisplayName("Meeting Name")]
  public string Name { get; set; }

  [DisplayName("CEO")]
  [Description("The Chief Executive Officer meeting assignment")]
  public MeetingAssignment CEO { get; set; }

  [DisplayName("CDO")]
  [Description("The Chief Delivery Officer meeting assignment")]
  public MeetingAssignment CDO { get; set; }

  [DisplayName("CFO")]
  [Description("The Chief Financial Officer meeting assignment")]
  public MeetingAssignment CFO { get; set; }

  [DisplayName("COO")]
  [Description("The Chief Operating Officer meeting assignment")]
  public MeetingAssignment COO { get; set; }

  [DisplayName("CPO")]
  [Description("The Chief Procurement Officer meeting assignment")]
  public MeetingAssignment CPO { get; set; }
}

public class MeetingAssignment
{
  [DisplayName("Attendee Name")]
  public string AttendeeFullname { get; set; }

  [DisplayName("Attendee Id")]
  public int AttendeeId { get; set; }

  [DisplayName("Role Name")]
  public string RoleFullname { get; set; }

  [DisplayName("Role Id")]
  public int RoleId { get; set; }
}

// Return schema for typed meeting object
// GET /api/v2/meetings/{meetingId}/schema
// e.g. /api/v2/meetings/2/schema

private IActionResult GetTypedMeetingSchema()
{
  var generator = new JSchemaGenerator
  {
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
    SchemaReferenceHandling = SchemaReferenceHandling.None,
    DefaultRequired = Required.DisallowNull
  };

  var schema = generator.Generate(typeof(ExecutiveMeeting));
  return Content(schema.ToString(), "application/json", Encoding.UTF8);
}

In the case where the object returned is a known type, we can easily annotate the class properties with System.ComponentModel.DisplayNameAttribute and System.ComponentModel.DescriptionAttribute which the JSchemaGenerator will identify and map them as title and description properties in the schema.

Something I learned the hard way is that a dynamic schema with a nullable type doesn’t work when loading the dynamic schema in Power Automate:

{
  "type": "object",
  "properties": {
    "attendeeFullname": {
      "type": "string" //<--- Works
    },
    "roleFullname": {
      "type": [ "string", "null" ] //<--- Doesn't work!
    }
  }
}

So make sure to either specify { DefaultRequired = Required.DisallowNull } when setting the JSchemaGenerator or annotate the property with Json.NET serialization attributes like the example shown here.

// Output - Meeting Payload
{
  "id": 2,
  "date": "2021-12-23T00:00:00",
  "name": "Delivery meeting",
  "ceo": { 
    "attendeeFullname": "Santa",
    "attendeeId": 1,
    "roleFullname": "Chief Executive Officer",
    "roleId": 1
  },
  "cdo": {
    "attendeeFullname": "Rudolph",
    "attendeeId": 2,
    "roleFullname": "Chief Delivery Officer",
    "roleId": 2
  },
  "cfo": {
    "attendeeFullname": "Grinch",
    "attendeeId": 3,
    "roleFullname": "Chief Financial Officer",
    "roleId": 3
  },
  "coo": {
    "attendeeFullname": "The Elf",
    "attendeeId": 4,
    "roleFullname": "Chief Operating Officer",
    "roleId": 4
  },
  "cpo": {
    "attendeeFullname": "Carol",
    "attendeeId": 5,
    "roleFullname": "Chief Procurement Officer",
    "roleId": 5
  }
}

// Output - Meeting Schema
{
  "type": "object",
  "properties": {
    "id": { "title": "Meeting Id", "type": "integer" },
    "date": { "title": "Meeting Date", "type": "string", "format": "date-time" },
    "name": { "title": "Meeting Name", "type": "string" },
    "ceo": {
      "title": "CEO",
      "description": "The Chief Executive Officer meeting assignment",
      "type": "object",
      "properties": {
        "attendeeFullname": {
         "title": "Attendee Name",
         "type": "string"
        },
        "attendeeId": {
          "title": "Attendee Id",
          "type": "integer"
        },
        "roleFullname": {
          "title": "Role Name",
          "type": "string"
        },
        "roleId": {
          "title": "Role Id",
          "type": "integer"
        }
      }
    },
    "cdo": { /* same as above */ },
    "cfo": { /* same as above */ },
    "coo": { /* same as above */ },
    "cpo": { /* same as above */ }
  }
}

 

#3. Dynamic schema parsed from JSON schema and validated against an untyped object

Scenario: This is the same as the previous scenario but instead of hardcoding the schema, the API will retrieve the schema from a data store like Dataverse so that the schema can be easily modified as required by users. The schema will need to be validated against the targeted object before returning.

// Return untyped meeting object
// GET /api/v2/meetings/{meetingId}
// e.g. /api/v2/meetings/3

private IActionResult GetGeneralMeeting(Meeting meeting)
{
  var generalMeeting = MapGeneralMeeting(meeting);
  return Content(generalMeeting.ToString(), "application/json", Encoding.UTF8);
}

private JObject MapGeneralMeeting(Meeting meeting)
{
  var mappedMeeting = new JObject
  {
    ["id"] = meeting.Id,
    ["name"] = meeting.Name,
    ["date"] = meeting.Date
  };

  foreach (var attendee in meeting.Attendees)
  {
    mappedMeeting[attendee.Role.Name.ToLower()] = attendee.Fullname;
  }

  return mappedMeeting;
}

// Return schema for typed meeting object
// GET /api/v2/meetings/{meetingId}/schema
// e.g. /api/v2/meetings/3/schema

private IActionResult GetValidatedMeetingSchema(Meeting meeting)
{
  var schemaJson = _sampleService.GetPredefinedMeetingSchema(meeting.Type);
  var schema = JSchema.Parse(schemaJson);

  var mappedMeeting = MapGeneralMeeting(meeting);
  var meetingObj = JObject.Parse(mappedMeeting.ToString());

  bool valid = meetingObj.IsValid(schema);
  if (valid)
    return Content(schemaJson, "application/json", Encoding.UTF8);

  return BadRequest();
}

// SampleService.cs
// Note: these are hard-coded here but the idea is to retrieve them from a data store like Dataverse
public string GetPredefinedMeetingSchema(MeetingType type)
{
    switch(type)
    {
      case MeetingType.General:
      return
        @"{
        ""type"": ""object"",
        ""properties"": {
          ""id"": {
            ""type"": ""integer"",
            ""title"": ""Meeting Id""
          },
          ""date"": {
            ""type"": ""string"",
            ""format"": ""date-time"",
            ""title"": ""Meeting Date""
          },
          ""name"": {
            ""type"": ""string"",
            ""title"": ""Meeting Name""
          },
          ""speaker1"": {
            ""type"": ""string"",
            ""description"": ""The full name of the speaker 1"",
            ""title"": ""Speaker 1""
          },
          ""speaker2"": {
            ""type"": ""string"",
            ""description"": ""The full name of the speaker 2"",
            ""title"": ""Speaker 2""
          },
          ""speaker3"": {
            ""type"": ""string"",
            ""description"": ""The full name of the speaker 3"",
            ""title"": ""Speaker 3""
          },
          ""mc"": {
            ""type"": ""string"",
            ""description"": ""The full name of the master of ceremonies"",
            ""title"": ""Master of Ceremonies""
          },
          ""tech"": {
            ""type"": ""string"",
            ""description"": ""The full name of the technician"",
            ""title"": ""Technician""
          }
        }
      }";

      default:
      return 
        @"{
          ""type"": ""object"",
          ""properties"": {
            ""id"": { ""type"": ""integer"" },
            ""date"": { ""type"": ""string"", ""format"": ""date-time"" },
            ""name"": { ""type"": ""string"" }
          }
        }";
    }
}

In the meeting payload below, I have specified one extra role – entertainment but haven’t specified this in the schema. When we load this in Power Automate action or Canvas App, the entertainment property won’t appear as it is not defined in the schema.

// Output - Meeting Payload
{
  "id": 3,
  "name": "Year 2021 send off meeting",
  "date": "2021-12-31T00:00:00",
  "speaker1": "Iron Man",
  "speaker2": "Captain America",
  "speaker3": "Thor",
  "mc": "Wolverine",
  "tech": "Hulk",
  "entertainment": "Spider-Man"
}

// Output - Meeting Schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "integer",
      "title": "Meeting Id"
    },
    "date": {
      "type": "string",
      "format": "date-time",
      "title": "Meeting Date"
    },
    "name": {
      "type": "string",
      "title": "Meeting Name"
    },
    "speaker1": {
      "type": "string",
      "description": "The full name of the speaker 1",
      "title": "Speaker 1"
    },
    "speaker2": {
      "type": "string",
      "description": "The full name of the speaker 2",
      "title": "Speaker 2"
    },
    "speaker3": {
      "type": "string",
      "description": "The full name of the speaker 3",
      "title": "Speaker 3"
    },
    "mc": {
      "type": "string",
      "description": "The full name of the master of ceremonies",
      "title": "Master of Ceremonies"
    },
    "tech": {
      "type": "string",
      "description": "The full name of the technician",
      "title": "Technician"
    }
  }
}

 

Use the Custom Connector From Power Automate Flow (Dynamic Content)

The Get Meeting action for the custom connector can be used in a Power Automate flow like this:

The result of flow execution would look like the following:

Depending on the meeting ID specified, different dynamic content will be available for the 3 scenarios covered:

ScenarioDynamic Content
#1. Dynamic schema for an untyped object
#2. Dynamic schema for a typed object
#3. Dynamic schema parsed from JSON for an untyped object

 

Use the Custom Connector From Canvas App (Dynamic Schema Feature)

In Canvas App, to utilise the dynamic schema returned by the custom connector we must first enable an experimental feature in the Settings section of the app called Dynamic schema:

Once this setting is enabled, we can call the operations on the custom connector that returns the dynamic schema and capture them, like taking snapshots, to use in the app. To demonstrate this, let’s first add the custom connector to the app and then place a button that calls the GetMeeting operation on button select:

As soon as we reference and try to use the GetMeeting operation, it will prompt a warning on the Canvas App element as well as the double underline in the Power Fx formula. The warning message will say – “Warning: Select ‘Capture Schema’ at the bottom of the expanded formula bar to set and refresh this method’s result schema. Otherwise this method will return no result”. Follow the instruction and expand the formula bar using the Chevron icon then you will see the Capture schema option:

When you click on this option, Power Apps Studio will request the dynamic schema for this particular operation from the custom connector and store it against the app. You can start referencing the properties from the other Canvas App elements like this:

You can read more detailed information about this feature in the Microsoft announcement post.

 

That’s it for now but I will cover more about this and other OpenAPI extensions + topics related to custom connectors in future posts! Hope you have a Merry Christmas and a Happy New Year!

Hope you enjoyed the post, let me know your thoughts in the comments below or any of my contact channels 😎