Files
Umbraco-CMS/src/Umbraco.Cms.Api.Delivery/CLAUDE.md
Sven Geusens c61bcca066 Add Claude memory files for all relevant project files (#20959)
* Regenerate delivery api claud memory file for updated file lines and inclusion of Secure Cookie-Based Token Storage

* Add delivery api memory file

* claude memory file for in memory modelsbuilder project

* Claud memory file for Imagesharp project

* Claude memory file for legacy image sharp project

* Claude memory files for Persistence projects

* Remaining claude memory files
2025-11-27 10:47:19 +01:00

12 KiB

Umbraco.Cms.Api.Delivery

Headless content delivery REST API for Umbraco CMS. Enables frontend applications to fetch published content, media, and member-protected resources.


1. Architecture

Type: Class Library (NuGet Package) Target Framework: .NET 10.0 Purpose: Content Delivery API for headless CMS scenarios

Key Technologies

  • ASP.NET Core - Web framework
  • OpenIddict - Member authentication (OAuth 2.0)
  • Asp.Versioning - API versioning (V1, V2)
  • Output Caching - Configurable response caching
  • Examine/Lucene - Content querying

Dependencies

  • Umbraco.Cms.Api.Common - Shared API infrastructure (OpenAPI, auth)
  • Umbraco.Web.Common - Web functionality

Project Structure (86 files)

Umbraco.Cms.Api.Delivery/
├── Controllers/
│   ├── Content/               # Content endpoints (by ID, route, query)
│   ├── Media/                 # Media endpoints (by ID, path, query)
│   └── Security/              # Member auth (authorize, token, signout)
├── Querying/
│   ├── Filters/               # ContentType, Name, CreateDate, UpdateDate
│   ├── Selectors/             # Ancestors, Children, Descendants
│   └── Sorts/                 # Name, CreateDate, UpdateDate, Level, SortOrder
├── Indexing/                  # Lucene index field handlers
├── Services/                  # Business logic and query building
├── Caching/                   # Output cache policies
├── Rendering/                 # Output expansion strategies
├── Configuration/             # Swagger configuration
└── Filters/                   # Action filters (access, validation)

Design Patterns

  1. Strategy Pattern - Query handlers (ISelectorHandler, IFilterHandler, ISortHandler)
  2. Factory Pattern - ApiContentQueryFactory builds Examine queries
  3. Template Method - ContentApiControllerBase for shared controller logic
  4. Options Pattern - DeliveryApiSettings for all configuration

2. Commands

See "Quick Reference" section at bottom for common commands.


3. Key Patterns

API Versioning (V1 vs V2)

V1 (legacy) and V2 (current) coexist. Key difference is output expansion:

// DependencyInjection/UmbracoBuilderExtensions.cs:49-52
// V1 uses RequestContextOutputExpansionStrategy
// V2+ uses RequestContextOutputExpansionStrategyV2
return apiVersion.MajorVersion == 1
    ? provider.GetRequiredService<RequestContextOutputExpansionStrategy>()
    : provider.GetRequiredService<RequestContextOutputExpansionStrategyV2>();

Why V2: Improved expand and fields query parameter parsing (tree-based).

Query System Architecture

Content querying flows through handlers registered in DI:

  1. Selectors (fetch parameter): ancestors:id, children:id, descendants:id
  2. Filters (filter[] parameter): contentType:alias, name:value, createDate>2024-01-01
  3. Sorts (sort[] parameter): name:asc, createDate:desc, level:asc
// Services/ApiContentQueryService.cs:91-96
ISelectorHandler? selectorHandler = _selectorHandlers.FirstOrDefault(h => h.CanHandle(fetch));
return selectorHandler?.BuildSelectorOption(fetch);

Path Decoding Workaround

ASP.NET Core doesn't decode forward slashes in route parameters:

// Controllers/DeliveryApiControllerBase.cs:21-31
// OpenAPI clients URL-encode paths, but ASP.NET Core doesn't decode "/"
// See https://github.com/dotnet/aspnetcore/issues/11544
if (path.Contains("%2F", StringComparison.OrdinalIgnoreCase))
{
    path = WebUtility.UrlDecode(path);
}

4. Testing

Location: No direct tests - tested via integration tests in test projects

dotnet test tests/Umbraco.Tests.Integration/ --filter "FullyQualifiedName~Delivery"

Internals exposed to (csproj lines 28-36):

  • Umbraco.Tests.UnitTests
  • Umbraco.Tests.Integration
  • DynamicProxyGenAssembly2 (for mocking)

Focus areas:

  • Query parsing (selectors, filters, sorts)
  • Member authentication flows
  • Output caching behavior
  • Protected content access

5. Security & Access Control

Three Access Modes

// Services/ApiAccessService.cs:21-27
public bool HasPublicAccess() => _deliveryApiSettings.PublicAccess || HasValidApiKey();
public bool HasPreviewAccess() => HasValidApiKey();
public bool HasMediaAccess() => _deliveryApiSettings is { PublicAccess: true, Media.PublicAccess: true } || HasValidApiKey();

Access levels:

  1. Public - No authentication required (if enabled)
  2. API Key - Via Api-Key header
  3. Preview - Always requires API key

Member Authentication

OpenIddict-based OAuth 2.0 for member-protected content:

Flows supported (Controllers/Security/MemberController.cs):

  • Authorization Code + PKCE (line 53)
  • Client Credentials (line 112)
  • Refresh Token (line 98)

Endpoints:

  • GET /umbraco/delivery/api/v1/security/member/authorize
  • POST /umbraco/delivery/api/v1/security/member/token
  • GET /umbraco/delivery/api/v1/security/member/signout

Scopes: Only openid and offline_access allowed for members (line 220-222)

Protected Content

Member access checked via ProtectedAccess model:

// Controllers/Content/QueryContentApiController.cs:56-57
ProtectedAccess protectedAccess = await _requestMemberAccessService.MemberAccessAsync();
Attempt<PagedModel<Guid>, ApiContentQueryOperationStatus> queryAttempt =
    _apiContentQueryService.ExecuteQuery(fetch, filter, sort, protectedAccess, skip, take);

6. Output Caching

Cache Policy Configuration

// DependencyInjection/UmbracoBuilderExtensions.cs:120-136
// Content and Media have separate cache durations
options.AddPolicy(
    Constants.DeliveryApi.OutputCache.ContentCachePolicy,
    new DeliveryApiOutputCachePolicy(
        outputCacheSettings.ContentDuration,
        new StringValues([AcceptLanguage, AcceptSegment, StartItem])));

Cache invalidation conditions (Caching/DeliveryApiOutputCachePolicy.cs:31):

// Never cache preview or non-public access
context.EnableOutputCaching = requestPreviewService.IsPreview() is false
    && apiAccessService.HasPublicAccess();

Vary by headers: Accept-Language, Accept-Segment, Start-Item


7. Edge Cases & Known Issues

Technical Debt (TODOs in codebase)

  1. V1 Removal Pending (4 locations):

    • DependencyInjection/UmbracoBuilderExtensions.cs:98 - FIXME: remove matcher policy
    • Routing/DeliveryApiItemsEndpointsMatcherPolicy.cs:11 - FIXME: remove class
    • Filters/SwaggerDocumentationFilterBase.cs:79,83 - FIXME: remove V1 swagger docs
  2. Obsolete Reference Warnings (csproj:9-13):

    • ASP0019 - IHeaderDictionary.Append usage
    • CS0618/CS0612 - Obsolete member references

Empty Query Results

Query service returns empty results (not errors) for invalid options:

// Services/ApiContentQueryService.cs:54-78
// Invalid selector/filter/sort returns fail status with empty result
return Attempt.FailWithStatus(ApiContentQueryOperationStatus.SelectorOptionNotFound, emptyResult);

Start Item Fallback

When no fetch parameter provided, uses start item or all content:

// Services/ApiContentQueryService.cs:99-112
if (_requestStartItemProviderAccessor.TryGetValue(out IRequestStartItemProvider? requestStartItemProvider))
{
    IPublishedContent? startItem = requestStartItemProvider.GetStartItem();
    // Use descendants of start item
}
return _apiContentQueryProvider.AllContentSelectorOption(); // Fallback to all

8. Project-Specific Notes

V1 vs V2 Differences

Feature V1 V2
Output expansion Basic Tree-based parsing
expand parameter Flat list Nested syntax
fields parameter Limited Full property selection
Default expansion strategy RequestContextOutputExpansionStrategy RequestContextOutputExpansionStrategyV2

Migration note: V1 is deprecated; plan removal when V17+ drops V1 support.

JSON Configuration

Delivery API has its own JSON options (distinct from Management API):

// DependencyInjection/UmbracoBuilderExtensions.cs:82-88
.AddJsonOptions(Constants.JsonOptionsNames.DeliveryApi, options =>
{
    options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    options.JsonSerializerOptions.TypeInfoResolver = new DeliveryApiJsonTypeResolver();
    options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});

Member Token Revocation

Tokens automatically revoked on member changes:

// DependencyInjection/UmbracoBuilderExtensions.cs:93-96
builder.AddNotificationAsyncHandler<MemberSavedNotification, RevokeMemberAuthenticationTokensNotificationHandler>();
builder.AddNotificationAsyncHandler<MemberDeletedNotification, RevokeMemberAuthenticationTokensNotificationHandler>();
builder.AddNotificationAsyncHandler<AssignedMemberRolesNotification, RevokeMemberAuthenticationTokensNotificationHandler>();
builder.AddNotificationAsyncHandler<RemovedMemberRolesNotification, RevokeMemberAuthenticationTokensNotificationHandler>();

External Dependencies

Examine/Lucene (via Core):

  • Powers content querying
  • Selector/Filter/Sort handlers build Lucene queries

OpenIddict (via Api.Common):

  • Member OAuth 2.0 authentication
  • Reference tokens (not JWT)

Configuration (appsettings.json)

{
  "Umbraco": {
    "CMS": {
      "DeliveryApi": {
        "Enabled": true,
        "PublicAccess": true,
        "ApiKey": "your-api-key",
        "Media": {
          "Enabled": true,
          "PublicAccess": true
        },
        "MemberAuthorization": {
          "AuthorizationCodeFlow": { "Enabled": true },
          "ClientCredentialsFlow": { "Enabled": false }
        },
        "OutputCache": {
          "Enabled": true,
          "ContentDuration": "00:01:00",
          "MediaDuration": "00:01:00"
        }
      }
    }
  }
}

API Endpoints Summary

Content (/umbraco/delivery/api/v2/content):

  • GET /item/{id} - Single content by GUID
  • GET /item/{path} - Single content by route
  • GET /items - Multiple by IDs
  • GET / - Query with fetch/filter/sort

Media (/umbraco/delivery/api/v2/media):

  • GET /item/{id} - Single media by GUID
  • GET /item/{path} - Single media by path
  • GET /items - Multiple by IDs
  • GET / - Query media

Security (/umbraco/delivery/api/v1/security/member):

  • GET /authorize - Start OAuth flow
  • POST /token - Exchange code for token
  • GET /signout - Revoke session

Quick Reference

Essential Commands

# Build project
dotnet build src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj

# Pack for NuGet
dotnet pack src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj -c Release

# Run integration tests
dotnet test tests/Umbraco.Tests.Integration/ --filter "FullyQualifiedName~Delivery"

# Check packages
dotnet list src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj package --outdated

Key Classes

Class Purpose File
DeliveryApiControllerBase Base controller with path decoding Controllers/DeliveryApiControllerBase.cs
ApiContentQueryService Query orchestration Services/ApiContentQueryService.cs
ApiAccessService Access control logic Services/ApiAccessService.cs
DeliveryApiOutputCachePolicy Cache policy implementation Caching/DeliveryApiOutputCachePolicy.cs
MemberController OAuth endpoints Controllers/Security/MemberController.cs
RequestContextOutputExpansionStrategyV2 V2 output expansion Rendering/RequestContextOutputExpansionStrategyV2.cs

Important Files

  • Umbraco.Cms.Api.Delivery.csproj - Project dependencies
  • DependencyInjection/UmbracoBuilderExtensions.cs - DI registration (lines 33-141)
  • Configuration/DeliveryApiConfiguration.cs - API constants
  • Services/ApiContentQueryService.cs - Query execution

Getting Help


This library exposes Umbraco content and media via REST for headless scenarios. Focus on query handlers, access control, and member authentication when working here.