---
title: Delivery API
description: Fetch the text editors changed, or every message, for one namespace or all of them, and merge it into your application, with next-intl and i18next examples.
seo:
  image: /og/delivery-api.png
---

Your application fetches translations one language per request, from one of two endpoints. **Settings > App translations** builds the URL for each language and [namespace](/sources#namespaces).

| Endpoint | Returns | Use it when |
| --- | --- | --- |
| `overrides` | Only the messages an editor changed. | Your application bundles its own text, and merges the changes over it. |
| `all` | Every message. An editor's text where there is one, your source's text everywhere else. | Your application has no text of its own, and takes it all from Umbraco. |

## Overrides

```http
GET /umbraco/delivery/api/v1/app-translations/overrides?locale=da-DK&namespace=website
```

```json
{
  "greeting": "Hej {name}, godt at se dig igen!"
}
```

Only `greeting` is here, because it is the only message an editor changed. Merge it over the messages your application already has.

## All messages

```http
GET /umbraco/delivery/api/v1/app-translations/all?locale=da-DK&namespace=website
```

```json
{
  "farewell": "Farvel",
  "greeting": "Hej {name}, godt at se dig igen!"
}
```

`greeting` is the editor's text. `farewell` has not been changed, so it is the text Umbraco read from your source.

## One namespace, several, or all of them

With one `namespace`, the response holds that namespace's messages, without the namespace around them, as in the examples above.

Repeat `namespace` to fetch several at once, or leave it out to fetch every namespace in the language. Each namespace is then a top-level key:

```http
GET /umbraco/delivery/api/v1/app-translations/all?locale=da-DK
```

```json
{
  "checkout": { "pay": "Betal nu" },
  "website": { "farewell": "Farvel", "greeting": "Hej {name}, godt at se dig igen!" }
}
```

Naming both namespaces returns the same response, and leaves out any other namespace:

```http
GET /umbraco/delivery/api/v1/app-translations/all?locale=da-DK&namespace=checkout&namespace=website
```

The order you name them in does not matter, and each namespace comes back under the name you used for it. A namespace that does not exist is left out of the response, and you can name up to 20 at once.

Repeat the parameter rather than separating names with commas: `namespace=checkout,website` asks for one namespace called `checkout,website`, and gets `400` when there is none.

A source that takes its namespaces from the first key of its file comes back in the same shape as that file. A source that puts every message in one namespace comes back under that namespace's name, so ask for the namespace by name if you want the shape of the file. From `overrides`, a namespace no editor has changed is left out.

## Message syntax

The JSON is the same whichever library reads it. What differs is the syntax inside a message: ICU's `{name}` and `{count, plural, ...}`, or i18next's `{{name}}` and `_one`/`_other` keys. Each source declares the syntax its messages are written in, and Umbraco serves them as written.

Add `format` to make sure a response holds nothing your application would misread. Umbraco then answers `409` rather than send a message in the other syntax.

| `format` | Syntax | Read by, for example |
| --- | --- | --- |
| `icu` | ICU MessageFormat | next-intl, react-intl and FormatJS, Lingui, messageformat |
| `i18next` | i18next JSON v4 | i18next, react-i18next, next-i18next |

Plain-text messages go with either. Without `format`, one response cannot mix ICU and i18next messages, so ask for namespaces in different syntaxes separately.

## Parameters

Both endpoints take `locale`, and optionally `namespace`, which you can repeat or leave out, and `format`. The API reference lists every parameter and response for [custom text](/endpoints/overrides) and [all messages](/endpoints/all).

:::caution
The locale is Umbraco's code, not your application's. A request for `da` when the site stores `da-DK` finds nothing and returns `{}`. Map your application's locales to Umbraco's, as the examples below do.
:::

A locale or namespace that has no messages also returns `{}`, not `404`, so an application keeps its own text rather than failing.

## next-intl

In a next-intl application, merge the custom text in the request configuration. This example uses next-intl 4 with the App Router and one `website` namespace:

```ts src/i18n/request.ts
import { getRequestConfig } from "next-intl/server";
import { hasLocale } from "next-intl";
import { routing } from "./routing";

// Your application's locales, and the Umbraco language codes they are stored under.
const umbracoLocales: Record<string, string> = { en: "en-US", da: "da-DK" };

type Messages = { [key: string]: string | Messages };

const merge = (base: Messages, overrides: Messages): Messages => {
  const merged: Messages = { ...base };
  for (const [key, value] of Object.entries(overrides)) {
    const current = merged[key];
    merged[key] =
      typeof value === "object" && typeof current === "object" ? merge(current, value) : value;
  }
  return merged;
};

export default getRequestConfig(async ({ requestLocale }) => {
  const requested = await requestLocale;
  const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale;
  const messages: Messages = (await import(`../../messages/${locale}.json`)).default;

  const url = new URL("/umbraco/delivery/api/v1/app-translations/overrides", process.env.UMBRACO_URL);
  url.search = new URLSearchParams({
    locale: umbracoLocales[locale],
    namespace: "website",
    format: "icu",
  }).toString();

  // If Umbraco cannot be reached, the application still has its own text.
  const response = await fetch(url, { next: { revalidate: 60 } }).catch(() => undefined);
  const overrides: Messages = response?.ok ? await response.json() : {};

  return { locale, messages: merge(messages, overrides) };
});
```

## i18next

When a source takes its namespaces from the first key of its file, they are the same as your i18next namespaces. Leave `namespace` out, and add each namespace in the response to i18next once it has loaded its own resources:

```ts
const url = new URL("/umbraco/delivery/api/v1/app-translations/overrides", UMBRACO_URL);
url.search = new URLSearchParams({ locale: "da-DK", format: "i18next" }).toString();

const response = await fetch(url);
if (response.ok) {
  const namespaces: Record<string, object> = await response.json();
  for (const [ns, messages] of Object.entries(namespaces)) {
    // Deep, and overwriting: the custom text replaces the bundled text key by key.
    i18next.addResourceBundle("da", ns, messages, true, true);
  }
}
```

With a source that puts every message in one namespace, ask for that namespace by name instead, and add the response to the i18next namespace your application uses, such as `translation`.

Use `format=i18next` and set the source's message syntax to **i18next JSON v4**. The output keeps nested keys, `{{variable}}` interpolation, context keys and plural suffixes such as `_one`, `_other` and `_ordinal_one`.

:::note
Umbraco never converts text between ICU and i18next syntax, so keep each namespace to one syntax. Umbraco refuses to serve a namespace that mixes them until you split it. If a language has ICU namespaces and i18next namespaces, fetch each namespace on its own, in the format that suits it.
:::

## Caching

Umbraco keeps each response in memory until the translations change: a sync that changed something, a saved or reverted message, or a deleted source. The change clears it on every server, so the next request reads the new text from the database. A response is also rebuilt after an hour, in case the database changed some other way, such as a restored backup.

Clients and CDNs may keep a response for 60 seconds by default, so an edit can take up to a minute to reach a client that caches. Set the lifetime with `AppTranslations:Delivery:MaxAgeSeconds`:

```sh
AppTranslations__Delivery__MaxAgeSeconds=0
```

With `0`, clients keep the response but check it on every request, and see an edit as soon as it is saved. Every response carries an ETag, so a client that sends `If-None-Match` gets `304 Not Modified` until something changes. Once Umbraco has built a response it answers those checks from memory; the first request after a change, and requests for a locale or namespace with no messages, still read the database. Umbraco keeps up to 100 responses for several namespaces at once. Past that, the one it has kept longest makes room, and is read from the database again the next time someone asks for it.

To drop a cached dictionary in your own application the moment it changes, subscribe to the [App translations updated webhook](/syncing#telling-your-application-something-changed).

## Access

The endpoints are public, which suits most sites: the text ends up in the application's bundle anyway. To keep them to your own applications, set a key:

```sh
AppTranslations__Delivery__ApiKey=replace-with-a-long-random-value
```

A request then needs the key in an `Api-Key` header, as Umbraco's own Delivery API expects, or as a bearer token, and gets `401` without it. A response to a keyed request is marked private, so a CDN does not hand it to anyone else. Keep the key on the server side of your application, never in code that runs in the browser.
