> For the complete documentation index, see [llms.txt](https://docs.umbraco.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.umbraco.com/umbraco-cms/extend-your-project/server-side-extensions/custom-backoffice-api.md).

# Custom Backoffice API

This article covers how to create a Custom API controller protected by the backoffice authorization policies. It also shows how to enable authorization in Swagger UI.

{% hint style="info" %}
Before proceeding, make sure to read the [Management API](/umbraco-cms/develop-with-umbraco/headless-and-apis/management-api.md) article. It provides information about the OpenAPI documentation and Authorization used in this article.
{% endhint %}

The following example can be a starting point for creating a secure custom API with automatic OpenAPI documentation. You can find other examples in the [API versioning and OpenAPI](/umbraco-cms/extend-your-project/server-side-extensions/api-versioning-and-openapi.md) article.

{% hint style="warning" %}
If you are building this in a class library, add the following property to the library's `.csproj`:

```xml
<PropertyGroup>
  <InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsNamespaces>
</PropertyGroup>
```

Without this property, the project fails to build with: `error CS9137: The 'interceptors' feature is not enabled in this compilation`.
{% endhint %}

1. Create a composer to register the OpenAPI document so that the new API shows in the OpenAPI documentation and Swagger UI:

{% code title="MyApiComposer.cs" lineNumbers="true" %}

```csharp
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Cms.Api.Management.OpenApi;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Composing;

namespace Umbraco.Cms.Web.UI.Custom;

public class MyApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
        => builder.AddBackOfficeOpenApiDocument(
            "my-api-v1",
            document => document
                .WithTitle("My API v1")
                .WithBackOfficeAuthentication()
                .WithJsonOptions(Constants.JsonOptionsNames.BackOffice));
}
```

{% endcode %}

`AddBackOfficeOpenApiDocument` registers a custom OpenAPI document with Umbraco's defaults applied:

* It includes controllers whose `[MapToApi]` attribute value matches the document name.
* It applies Umbraco's schema and operation ID conventions.
* It adds the document to the Swagger UI dropdown.

The builder methods configure additional behavior:

* `WithBackOfficeAuthentication()` enables OAuth2-based backoffice authorization in Swagger UI.
* `WithJsonOptions(Constants.JsonOptionsNames.BackOffice)` aligns schema generation with how the backoffice serializes responses at runtime.

2. Create a new file `MyApiController.cs` with the following controller:

{% code title="MyApiController.cs" lineNumbers="true" %}

```csharp
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Common.Filters;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Web.UI.Custom;

[ApiController]
[ApiVersion("1.0")]
[MapToApi("my-api-v1")]
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
[JsonOptionsName(Constants.JsonOptionsNames.BackOffice)]
[Route("api/v{version:apiVersion}/my")]
public class MyApiController : Controller
{
    private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;

    public MyApiController(IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
        => _backOfficeSecurityAccessor = backOfficeSecurityAccessor;

    [HttpGet("say-hello")]
    [MapToApiVersion("1.0")]
    [ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
    public IActionResult SayHello()
    {
        IUser currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser
                            ?? throw new InvalidOperationException("No backoffice user found");
        return Ok($"Hello, {currentUser.Name}");
    }
}
```

{% endcode %}

`[JsonOptionsName(Constants.JsonOptionsNames.BackOffice)]` tells the controller to serialize responses using the backoffice JSON options. This must match the `WithJsonOptions` call in the composer so the OpenAPI schema reflects the actual serialization output.

3. Run the project and navigate to `{yourdomain}/umbraco/openapi`.
4. Choose the OpenAPI document created with the code above named **My API v1** from **Select a definition**.

![Created Custom API in OpenAPI documentation](/files/VoypBf4pkoVFasELnWaM)

Here, you can find the endpoint that was created:

```http
GET /api/v1/my/say-hello
```

5. Click on the **Authorize** button to authenticate.
6. Try out the endpoint using the **Try it out** button.
7. Click on **Execute**.

![Trying out the endpoint](/files/EBPV6u2XufxB4MoctLSU)

You now get the response you have set up using the code: `"Hello, <user name>"`.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.umbraco.com/umbraco-cms/extend-your-project/server-side-extensions/custom-backoffice-api.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
