---
title: "ADR-244: A site names its configuration and its DeepL key"
manual: "LLM Foundation"
version: "0.43"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:adr-244@0.43"
source: "Adr/Adr244ASiteNamesItsConfigurationAndItsDeepLKey.rst"
rendered: "2026-11-04T13:45:29+00:00"
---

# ADR-244: A site names its configuration and its DeepL key {#adr-244}

-   *Status:* Accepted
-   *Date:* 2026-11-02
-   *Authors:* Netresearch DTT GmbH

## Context {#context}

An installation with several sites, often several clients, had one default
configuration and one DeepL key for all of them. [ADR-238](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-238@0.43) gave
term lists a site, read from the metadata key `nrllm.siteIdentifier`, but no
caller filled it. `TranslationOptions::withSite()` existed for the glossary
([ADR-208](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-208@0.43)) only.

## Decision {#decision}

**Two site settings in the set** `netresearch/nr-llm`: `nrllm.configuration`
holds the identifier of an LLM configuration, `nrllm.deepl.apiKeyIdentifier`
the identifier of an nr-vault secret. A key never sits in a site setting,
because `settings.yaml` is versioned with the project; this is how a provider
key and the global DeepL key are stored too.

**The site replaces the installation default and nothing else.** An explicit
choice wins: a pinned provider, a configuration the caller passes, an
extension's own named configuration. Below that comes the configuration the
site names, then the installation default. `chat()`, `complete()` and
`streamChat()` apply it for a call that states its site;
`ConfigurationResolver::resolveForSite()` lets an extension with its own
fallback put the site in the same place.

**A site that names something unusable fails the call.** A configuration that
does not exist or is disabled, a group-restricted one on a path without an
actor to check — `chat()`, `complete()` and `streamChat()` have none —
and a DeepL identifier the vault does not hold are errors.
`resolveForSite()` with an actor applies the group rule instead. Falling
back would send the site's text to the provider or the DeepL account the site
was set up to avoid.

**The caller states the site.** `withSite()` moves from `TranslationOptions`
to `AbstractOptions`; it and the metadata key end in the same place. nr-llm
does not derive the site from the request, because a worker has none.

**DeepL binds per call.** `DeepLTranslator::forSite()` returns a copy with the
site's identifier. The shared service keeps the global one, and Free or Pro is
remembered per identifier, so a worker serving two sites does not send one
site's key to the other's endpoint. A site glossary carries its site, so its
DeepL glossary is created in that site's account; one created earlier under
another key is refused by DeepL and recreated (ADR-208).

**Who may change it:** administrators. The settings editor is the
`site_settings` module in TYPO3 13.4 and the `editSettings` route of
`site_configuration` in 14.3, both with access `admin`. An administrator
who points the DeepL setting at another vault entry gains nothing, since an
administrator can already reach the vault.

## Consequences {#consequences}

-   ● One installation serves sites with their own provider, model and DeepL
    account, and term lists bound to a site work.
-   ● The settings hold identifiers only; no secret enters the repository.
-   ◑ A site has to depend on the set for the editor to show the settings.
-   ◑ Embeddings, vision, conversations and the image and speech services do not
    read the site's configuration.
-   ✕ A call whose caller does not state its site gets the installation default.

Net Score: +3

## Alternatives considered {#alternatives-considered}

**The DeepL key in the site setting.** Rejected: it would be committed with the
site configuration.

**Fall back to the installation default when the site's choice is unusable.**
Rejected: silent, and it defeats the reason a site names its own choice.

**Derive the site from the current request.** Rejected: the worker, the
scheduler and the CLI have no request, and a backend request is not always
about the page it was sent from.
