---
title: Your own APIs
description: Tag responses from your site's own controllers, record changes to their data, and purge by hand.
seo:
  image: /og/your-apis.png
---

Your site's own API endpoints can carry cache keys too, so a frontend can cache their responses and the package purges them. The repository's sample does this for an endpoint that serves Umbraco's dictionary items.

## Tag the responses

Put `[CacheTagged]` on a controller or action, and name what each response covers with the scoped `ICacheTagsResponse`:

```csharp
[ApiController]
[Route("api/dictionary")]
[CacheTagged]
public sealed class DictionaryController(ILanguageService languages, ICacheTagsResponse cacheTags) : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> Get([FromQuery] string locale)
    {
        ILanguage? language = await languages.GetAsync(locale);
        if (language is null)
        {
            return NotFound();
        }

        cacheTags.Add($"x-dictionary-{language.IsoCode.ToLowerInvariant()}");
        // ... read the dictionary items in that language
        return Ok(items);
    }
}
```

Like a Delivery API response, the response carries `umbraco` and the keys of what it shows: the keys you name, and the keys of the Delivery API items in its result, which the package finds by itself.

- **Custom keys** start with `x-` and use lowercase letters, digits and hyphens, at most 200 characters. An item's `content-` or `media-` key works too.
- **Only a 200 is tagged.** The package tags a response before its result runs, so it reads the status from the result: return an `ObjectResult` (`Ok(...)`), a `JsonResult`, a `ContentResult`, a status code result, a file, an `EmptyResult`, or a typed result (`TypedResults.Ok(...)`, `Results<...>`) with a status code. Anything else, such as a redirect, a challenge or a result type the package does not know, gets `no-store` and no key.

## Record changes

When the data behind those responses changes, record a change with `ICacheTagsChanges`:

```csharp
public sealed class DictionaryCacheTags(ICacheTagsChanges changes, ILanguageService languages)
    : INotificationAsyncHandler<DictionaryItemSavedNotification>,
      INotificationAsyncHandler<DictionaryItemDeletedNotification>,
      INotificationHandler<DictionaryCacheRefresherNotification>
{
    public Task HandleAsync(DictionaryItemSavedNotification notification, CancellationToken cancellationToken)
        => RecordAsync("dictionary items saved");

    public Task HandleAsync(DictionaryItemDeletedNotification notification, CancellationToken cancellationToken)
        => RecordAsync("dictionary items deleted");

    // Raised on every server, once its dictionary cache has the change.
    public void Handle(DictionaryCacheRefresherNotification notification) => changes.Applied("dictionary");

    // A dictionary item has text in every language, so a change reaches every language's response.
    private async Task RecordAsync(string reason)
        => changes.Record(
            (await languages.GetAllAsync()).Select(language => $"x-dictionary-{language.IsoCode.ToLowerInvariant()}"),
            waitFor: "dictionary",
            reason: reason);
}
```

Register it in a composer:

```csharp
builder.AddNotificationAsyncHandler<DictionaryItemSavedNotification, DictionaryCacheTags>();
builder.AddNotificationAsyncHandler<DictionaryItemDeletedNotification, DictionaryCacheTags>();
builder.AddNotificationHandler<DictionaryCacheRefresherNotification, DictionaryCacheTags>();
```

- **`waitFor`** names a signal. The package sends the purge once every server has reported the signal with `Applied(signal)`. Call it wherever a server refreshes its copy of the data, usually in a cache refresher's notification handler. Signals use lowercase letters, digits, dots and hyphens.
- **Without `waitFor`**, the package sends the purge at once. Use that for data that every server reads straight from the database.
- **`reason`** is shown with the change in the backoffice.

## Purge by hand

Administrators can purge keys under **Settings > Advanced > Cache tags > Purge**, or through the Management API:

| Request | Does |
| --- | --- |
| `POST /umbraco/management/api/v1/cache-tags/purge` | Purges the keys in the JSON array body at once: `x-` keys, items' `content-` and `media-` keys, or `["umbraco"]` for everything. |
| `GET .../cache-tags/purges?state=Dead` | Lists purges in a state: `Pending`, `Sent`, `Dead` or `Discarded`. |
| `POST .../cache-tags/purges/{id}/retry` | Sends a failed or set-aside purge again. |
| `POST .../cache-tags/purges/{id}/discard` | Sets a pending or failed purge aside. |
