skills/backend/umbraco-openapi-client/SKILL.md
Set up OpenAPI client for authenticated API calls in Umbraco backoffice (REQUIRED for custom APIs)
npx skillsauth add albanist/umbraco_cli umbraco-openapi-clientInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
NEVER use raw fetch() calls for Umbraco backoffice API communication. Raw fetch calls will result in 401 Unauthorized errors because they don't include the bearer token authentication that Umbraco requires.
ALWAYS use a generated OpenAPI client configured with Umbraco's auth context. This ensures:
Use this pattern whenever you:
[BackOfficeRoute]The setup has 4 parts:
@hey-api/openapi-ts and @hey-api/client-fetchYour API must be exposed via Swagger. Create a composer:
// Composers/MyApiComposer.cs
using Asp.Versioning;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
namespace MyExtension.Composers;
public class MyApiComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
// Register the API and Swagger
builder.Services.AddSingleton<ISchemaIdHandler, MySchemaIdHandler>();
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, MySwaggerGenOptions>();
builder.Services.Configure<UmbracoPipelineOptions>(options =>
{
options.AddFilter(new UmbracoPipelineFilter(Constants.ApiName)
{
SwaggerPath = $"/umbraco/swagger/{Constants.ApiName.ToLower()}/swagger.json",
SwaggerRoutePrefix = $"{Constants.ApiName.ToLower()}",
});
});
}
}
// Swagger schema ID handler
public class MySchemaIdHandler : SchemaIdHandler
{
public override bool CanHandle(Type type)
=> type.Namespace?.StartsWith("MyExtension") ?? false;
}
// Swagger generation options
public class MySwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
{
public void Configure(SwaggerGenOptions options)
{
options.SwaggerDoc(
Constants.ApiName,
new OpenApiInfo
{
Title = "My Extension API",
Version = "1.0",
});
}
}
// Constants
public static class Constants
{
public const string ApiName = "myextension";
}
Add to your Client/package.json:
{
"scripts": {
"generate-client": "node scripts/generate-openapi.js https://localhost:44325/umbraco/swagger/myextension/swagger.json"
},
"devDependencies": {
"@hey-api/client-fetch": "^0.10.0",
"@hey-api/openapi-ts": "^0.66.7",
"chalk": "^5.4.1",
"node-fetch": "^3.3.2"
}
}
Create Client/scripts/generate-openapi.js:
import fetch from "node-fetch";
import chalk from "chalk";
import { createClient, defaultPlugins } from "@hey-api/openapi-ts";
console.log(chalk.green("Generating OpenAPI client..."));
const swaggerUrl = process.argv[2];
if (swaggerUrl === undefined) {
console.error(chalk.red(`ERROR: Missing URL to OpenAPI spec`));
process.exit(1);
}
// Ignore self-signed certificates on localhost
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
console.log(`Fetching OpenAPI definition from ${chalk.yellow(swaggerUrl)}`);
fetch(swaggerUrl)
.then(async (response) => {
if (!response.ok) {
console.error(chalk.red(`ERROR: ${response.status} ${response.statusText}`));
return;
}
await createClient({
input: swaggerUrl,
output: "src/api",
plugins: [
...defaultPlugins,
{
name: "@hey-api/client-fetch",
bundle: true,
exportFromIndex: true,
throwOnError: true,
},
{
name: "@hey-api/typescript",
enums: "typescript",
},
{
name: "@hey-api/sdk",
asClass: true,
},
],
});
console.log(chalk.green("Client generated successfully!"));
})
.catch((error) => {
console.error(`ERROR: ${chalk.red(error.message)}`);
});
Configure the generated client with Umbraco's auth context in your entry point:
// src/entrypoints/entrypoint.ts
import type { UmbEntryPointOnInit, UmbEntryPointOnUnload } from "@umbraco-cms/backoffice/extension-api";
import { UMB_AUTH_CONTEXT } from "@umbraco-cms/backoffice/auth";
import { client } from "../api/client.gen.js";
export const onInit: UmbEntryPointOnInit = (host, _extensionRegistry) => {
// CRITICAL: Configure the OpenAPI client with authentication
host.consumeContext(UMB_AUTH_CONTEXT, (authContext) => {
if (!authContext) return;
const config = authContext.getOpenApiConfiguration();
client.setConfig({
baseUrl: config.base,
credentials: config.credentials,
auth: config.token, // This provides the bearer token!
});
console.log("API client configured with auth");
});
};
export const onUnload: UmbEntryPointOnUnload = (_host, _extensionRegistry) => {
// Cleanup if needed
};
After running npm run generate-client, use the generated service:
// In your workspace context, repository, or data source
import { MyExtensionService } from "../api/index.js";
// The client handles auth automatically!
const response = await MyExtensionService.getItems({
query: { skip: 0, take: 50 },
});
const item = await MyExtensionService.getItem({
path: { id: "some-guid" },
});
await MyExtensionService.createItem({
body: { name: "New Item", value: 123 },
});
npm run generate-clientsrc/api/:
types.gen.ts - TypeScript types from your C# modelssdk.gen.ts - Service class with typed methodsclient.gen.ts - HTTP client configurationindex.ts - Re-exports everything// This will get 401 Unauthorized!
const response = await fetch('/umbraco/myextension/api/v1/items');
// Still fails - cookies don't work for Management API
const response = await fetch('/umbraco/myextension/api/v1/items', {
credentials: 'include'
});
// Client is configured with bearer token in entry point
const response = await MyExtensionService.getItems();
See the complete working implementation in:
examples/notes-wiki/Client/ - Full OpenAPI client setupexamples/tree-example/Client/ - Tree with OpenAPI integrationComposers/MyApiComposer.cs - Swagger registrationClient/scripts/generate-openapi.js - Generation scriptClient/src/entrypoints/entrypoint.ts - Auth configurationClient/src/api/ - Generated (don't edit manually)That's it! Always generate your API client and configure it with auth. Never use raw fetch for authenticated endpoints.
tools
Umbraco Automate operations (event-driven workflow automation)
development
Webhook management (the Management API's outbound event notifications)
development
Backoffice user management (accounts, state, groups, API credentials)
tools
Backoffice user group management (permission sets)