Expose openapi documentation on Azure API Management

Azure API management automatically exposes openapi documentation through the developer portal. But what if your company doesn’t activate the portal? This article describes how you can make the openapi documentation available through your API.

The location of the openapi documentation

Via the azure portal you can find the location of the openapi documentation. Go to your API, click on the ellipsis and export the definition:

export openapi

Open the developer tools of your browser before you click on the desired format (in this article I”ll work with the OpenAPI v3 YAML)

location openapi

So, the location is like

https://management.azure.com/subscriptions/[subscriptionid]/resourceGroups/[resourcegroupname]/Microsoft.ApiManagement/service/[servicename]/apis/[apiid]?export=true&format=openapi&api-version=2021-01-01-preview

There are 4 unique parts in this url:

  • subscriptionid: the azure subscription id where your APIM service is running
  • resourcegroupname: The resource group where your APIM service is running
  • servicename: The name of the APIM service
  • apiid: The id of the API

No management API

After seeing the url you might thing you need to enable the Management API of your APIM service. However, the management API starts with yourapimservicename.management.azure-api.net. The url we got in the previous step is a general management url. So we don’t not need to enable the specific management API to get our openapi documentation.

no management API

Keep in mind that the management API of your apim service enables admin-level access on your APIM instance, so please avoid enabling it.

Authenticate via Managed identity

To call the url with the openapi documentation, we need to be authenticated. Enable managed identity on your APIM service

enable managed identity

Now, our APIM service has an identity, but it still doesn’t have the rights to call the url.

In the same window where you enabled managed identity, you can click on “Azure role assignments”. Give your managed identity the “API Management Service Reader Role” on Resource group level.

apim reader role

or via powershell:

$resourceGroup = Get-AzResourceGroup -Name "the-apim-resourcegroup-name"
New-AzRoleAssignment -ObjectId "the-managed-idendentity-of-your-apim" -RoleDefinitionName "API Management Service Reader Role" -Scope $resourceGroup.ResourceId

Define a documentation endpoint

Now you want to define a documentation endpoint where people can download the openapi spec. On our api, we’ll add an endpoint openapi.yaml. If your API has a baseUrl like https://your-apim-instance.azure-api.net/person/v1 we want the openapi documentation file be available on https://your-apim-instance.azure-api.net/person/v1/openapi.yaml

$ResourceGroupName='the-apim-resourcegroup-name'
$apiId='person-api-v1' # this one corresponds with the apiid you found in the url of the first step
$openApiOperationId='get-openapi-yaml'
$apimService=Get-AzResource -ResourceType "Microsoft.ApiManagement/service" -ResourceGroupName $ResourceGroupName
$apimCtx=New-AzApiManagementContext -ResourceGroupName $ResourceGroupName -ServiceName $apimService.Name
Get-AzApiManagementOperation -Context $apimCtx -ApiId $apiId -OperationId $openApiOperationId -ErrorVariable openApiSpecNotPresent -ErrorAction SilentlyContinue
if($openApiSpecNotPresent)
{
    Write-Verbose "Create endpoint for openapi spec on openapi.yaml"
    New-AzApiManagementOperation -Context $apimCtx -ApiId $apiId -OperationId $openApiOperationId -Name 'openapi' -Method 'GET' -UrlTemplate 'openapi.yaml'
}
else{
    Write-Verbose "Endpoint for openapi spec openapi.yaml already exists"
}

The policy to call the openapi documentation

The last thing we need to do is add a policy file on our “get-openapi-yaml” operation to call the url we found in the first step. We’ll make the 4 unique parts of the url dynamic so we can reuse the policy:

  • subscriptionid: we’ll replace it with an APIM named value {{azure-subscriptionid}}
  • resourcegroupname: we’ll replace it with an APIM named value {{azure-resourcegroup}}
  • servicename: is available in the context of your policy file via context.Deployment.ServiceName
  • apiid: is available in the context of your policy file via context.Api.Id

As the call needs to be authenticated, we’ll add the policy authentication-managed-identity and ask access to https://management.azure.com/

The policy file looks like:

<policies>
    <inbound>
        <base />
        <send-request mode="new" response-variable-name="result" timeout="300" ignore-error="false">
            <set-url>@("https://management.azure.com/subscriptions/{{azure-subscriptionid}}/resourceGroups/{{azure-resourcegroup}}/providers/Microsoft.ApiManagement/service/" + context.Deployment.ServiceName + "/apis/" + context.Api.Id + "?export=true&format=openapi&api-version=2021-01-01-preview")</set-url>
            <set-method>GET</set-method>
            <authentication-managed-identity resource="https://management.azure.com/" />
        </send-request>
        <return-response>
            <set-status code="200" reason="OK" />
            <set-header name="Content-Type" exists-action="override">
                <value>application/yaml</value>
            </set-header>
            <set-body>@((string)(((IResponse)context.Variables["result"]).Body.As<JObject>()["value"]))</set-body>
        </return-response>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

The only thing left is link the policy file with the openapi.yaml endpoint:

Set-AzApiManagementPolicy -Context $apimCtx -ApiId 'person-api-v1' -OperationId 'get-openapi-yaml' -PolicyFilePath 'openapispec.policy.xml'

Conclusion

It is possible to expose your openapi documentation via an API call. Again, Managed Identity helped me out to expose services in a secure way. No need to enable the admin-level Management API or hassle with SAS tokens. The policy that handles the request can be reused for all your APIs.

References