Azure Functions can automatically generate OpenAPI specifications, but it’s not a magic bullet; you still need to understand how it’s generated to get useful results.

Let’s see this in action. Imagine you have a simple HTTP-triggered Azure Function in C# that takes a name and returns a greeting.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

public static class HttpTriggerCSharp
{
    [FunctionName("HttpTriggerCSharp")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");

        string name = req.Query["name"];

        string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        dynamic data = JsonConvert.DeserializeObject(requestBody);
        name = data?.name ?? name;

        string responseMessage = string.IsNullOrEmpty(name)
            ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
            : $"Hello, {name}! This HTTP triggered function executed successfully.";

        return new OkObjectResult(responseMessage);
    }
}

To enable OpenAPI generation, you need to add a few NuGet packages and a configuration. First, ensure you have Microsoft.Azure.WebJobs.Extensions.OpenApi installed. Then, in your Startup.cs (or equivalent if you’re not using dependency injection extensively), you’d configure the OpenAPI details.

Here’s a snippet of how you might configure it in Startup.cs for a .NET 6+ isolated worker model:

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
using Microsoft.OpenApi.Models;

public class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsWorkerApplicationBuilder app)
    {
        app.UseFunctionsWorkerDefaults();

        app.UseOpenApi(); // This is the key extension method

        app.AddOpenApiConfiguration("AzureFunctionsOpenAPI", settings =>
        {
            settings.Title = "Azure Functions OpenAPI";
            settings.Version = "1.0.0";
            settings.Description = "This is a sample Azure Function OpenAPI specification.";
            settings.Contact = new OpenApiContact()
            {
                Name = "Your Name",
                Email = "your.email@example.com"
            };
            settings.License = new OpenApiLicense()
            {
                Name = "MIT",
                Url = "https://opensource.org/licenses/MIT"
            };
            settings.Servers.Add(new OpenApiServer() { Url = "https://your-function-app-name.azurewebsites.net" });
            settings.IncludeJourneys = false; // Typically false for functions
            settings.ForceHttp = false; // Use HTTPS if available
            settings.ForceHttps = true;
            settings.CustomUIPath = "/swagger-ui"; // Default is /swagger-ui
            settings.ApiSpecVersion = OpenApiSpecVersion.OpenApi3_0;
        });
    }
}

With this setup, when your function app runs, it will expose an endpoint (usually /api/swagger.json by default, or the CustomUIPath you configure) containing the OpenAPI specification. The Microsoft.Azure.WebJobs.Extensions.OpenApi package uses reflection and attributes to infer the API’s structure. For simple HTTP triggers, it can detect the HTTP methods allowed (get, post), the route, and basic request/response structures.

The problem this solves is the manual, error-prone process of writing and maintaining API documentation. Instead of manually creating a swagger.json or openapi.yaml file, the Azure Functions tooling attempts to derive it from your code. This is particularly helpful for microservices where many small functions might expose their own endpoints.

Internally, the Microsoft.Azure.WebJobs.Extensions.OpenApi extension scans your Azure Functions for attributes that define API endpoints, such as HttpTrigger and OpenApiOperation. It then builds an OpenApiDocument object based on this information, including paths, operations, parameters, request bodies, and responses. For C# functions, it leverages OpenApiSchema attributes and other metadata to describe complex types.

Consider a scenario where your function expects a JSON payload. Without explicit schema definitions, the generator might infer a generic "object" type. To get a truly useful specification, you’ll want to annotate your input and output models with attributes that the OpenAPI extension can understand.

For instance, if your function accepted a specific request body:

public class MyRequestModel
{
    [Required] // This attribute helps OpenAPI know it's a required field
    public string Name { get; set; }
    public int Age { get; set; }
}

And your function signature changed to:

public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
    ILogger log,
    [FromBody] MyRequestModel requestBody) // Use [FromBody] for the model
{
    // ...
}

The OpenAPI generator, with the Microsoft.Azure.WebJobs.Extensions.OpenApi package, can then generate a more precise schema for the MyRequestModel, including its properties (Name, Age) and their types, and mark Name as required. You can further enhance this with OpenApiProperty attributes for descriptions, examples, and more specific data types.

One aspect that often surprises developers is how little the OpenAPI generator understands about your intent without explicit guidance. While it can detect an HttpTrigger and a POST method, it won’t magically know the exact shape of your request or response payload unless you provide it. This means you’ll often end up with generic object types in your spec unless you decorate your models and parameters with attributes like [OpenApiRequestBody], [OpenApiResponse], [OpenApiParameter], and [OpenApiSchema] from the Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes namespace. For example, to explicitly define a request body schema, you’d add an attribute like this to your function method:

[OpenApiRequestBody(contentType: "application/json", bodyType: typeof(MyRequestModel), Required = true)]

This explicit declaration is crucial for generating a specification that is both accurate and useful for consumers of your API.

The next step after generating your OpenAPI spec is usually integrating it with API management tools or client code generators.

Want structured learning?

Take the full Azure-functions course →