> ## Documentation Index
> Fetch the complete documentation index at: https://veriqa.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Справочник конфигурации

> Каждая секция, ключ и дефолт конфигурации Veriqa в одном месте.

Veriqa читает стандартный `IConfiguration` хоста, поэтому любая настройка может прийти из
`appsettings.json`, переменных окружения, user-secrets или любого другого провайдера. Всё, что
принадлежит Veriqa, живёт под единственным корневым ключом `Veriqa`:

| Секция | Содержимое |
| - | - |
| `Veriqa` | Сервер аутентификации: OIDC, транзакции, страница входа, лимиты, инфраструктура |
| `Veriqa:Channels` | Адаптеры каналов: MAX, Telegram, WhatsApp, Email |

Справочник отвечает на вопрос «чем это настраивается». На соседние два вопроса отвечают
отдельные страницы: что вообще поддерживается из спецификаций —
[Поддержка OIDC и OAuth 2.0](/docs/ru/reference/oidc-capabilities), чего сегодня нет —
[Известные ограничения](/docs/ru/reference/limitations).

В переменных окружения `:` превращается в `__` — `Veriqa:OpenIddict:Database:Provider` это
`Veriqa__OpenIddict__Database__Provider`.

Здесь стоит знать про одно ограничение оболочки. В имени переменной POSIX-оболочка допускает только
буквы, цифры и `_`, поэтому `export` отвергает имя целиком, как только `-` или `.` содержит **любой** сегмент
пути: `export Veriqa__ChannelDisplay__Hints__my-channel=...` отклоняется как недопустимый
идентификатор. Откуда взялся такой сегмент, роли не играет. Это может быть **значение**, которое
выбираете вы: тип канала в `Veriqa:AuthPageDesign:QrCode:PixelsPerModuleByChannel:<тип канала>` и в
`Veriqa:ChannelDisplay:Hints:<тип канала>` (типу канала SPI-адаптера дефис разрешён) или код записи
в каталоге `Veriqa:UiConfigurations:Records`. Ровно так же это может быть **фиксированное имя
ключа** схемы, которое вы не выбираете вовсе, — вид сообщения в
`Veriqa:MessageTemplates:confirmation-prompt`.

Само имя переменной при этом законно, и ограничение обходится штатными средствами: Docker (`-e`),
переменные Kubernetes (`env:`), `launchSettings.json` и вызов `env ИМЯ=ЗНАЧЕНИЕ команда` такие имена
принимают, а .NET читает их как обычно. Путь, все сегменты которого состоят из букв, цифр и `_`,
задаётся переменной окружения обычным порядком.

**Отдельный каталог конфигурации.** Одна переменная задаёт не настройку, а место, откуда настройки
читать: `VERIQA_CONFIG_DIRECTORY` указывает на **каталог**, и найденные там `appsettings.json` и
`appsettings.<Environment>.json` хост накладывает поверх файлов, поставляемых с программой, и под
переменными окружения. Оба файла необязательны; несуществующий каталог останавливает старт, пустой
ошибкой не является. Так хранит свою конфигурацию установка
[службой ОС](/docs/ru/quickstart/os-service) — `/etc/veriqa` на Linux, `%ProgramData%\Veriqa` на
Windows, — там её не заденет замена программы.

<Note>
  Конфигурация валидируется на **старте**. Неизвестное значение enum, реляционный провайдер без
  строки подключения или включённый канал без токена останавливают приложение немедленно, а не
  падают позже на первом входе пользователя.
</Note>

## Veriqa:OpenIddict

OIDC-сервер, его хранилище и зарегистрированные клиенты.

### Database

| Ключ | Дефолт | Значение |
| - | - | - |
| `Provider` | — (умолчания нет) | Хранилище клиентов, авторизаций и токенов: `InMemory`, `PostgreSQL` или `SqlServer` |
| `ConnectionString` | — | Обязателен для любого реляционного провайдера |

Умолчания у `Provider` нет: незаданное значение — не «`InMemory` по умолчанию», а несделанный выбор.
Вне окружения `Development` такой хост **не стартует** (проверка на старте называет лечение), в
`Development` — поднимается с `Warning`. `InMemory` остаётся допустимым значением, но задаётся
явно, и каждый рестарт инвалидирует выданные токены — подходит для разработки и для
[коннектора на одном инстансе](/docs/ru/guides/storage#коннектор-на-одном-инстансе-без-базы-данных).

Во встроенном режиме эти ключи провайдер не выбирают — там его выбирает ваш хост вызовом
`authServer.UseOpenIddictDatabase(...)` либо `authServer.UseInMemoryOpenIddictStore()`;
в self-hosted-режиме выбор задаётся этими ключами.

### Server

| Ключ | Дефолт | Значение |
| - | - | - |
| `Issuer` | URL хоста | Явный issuer; нужен, когда публичный URL отличается от привязанного |
| `AccessTokenLifetimeSeconds` | `3600` | Время жизни access-токена |
| `RefreshTokenLifetimeSeconds` | `1209600` | Время жизни refresh-токена (14 дней) |
| `AuthorizationCodeLifetimeSeconds` | `300` | Время жизни authorization code |
| `EnableRefreshTokenRotation` | `true` | Выдавать новый refresh-токен при каждом использовании |
| `EnableRevocation` | `true` | Включить `/connect/revoke` |
| `TokenPruneIntervalSeconds` | `3600` | Как часто фоновая задача удаляет устаревшие записи токенов и авторизаций из базы OpenIddict. Больше 0 и не больше `4294967` (около 49,7 суток — самое долгое ожидание, которое принимает таймер), иначе старт останавливается |
| `TokenPruneThresholdSeconds` | `86400` | Минимальный возраст записи токена или авторизации, считая от её создания, после которого задача вправе её удалить, — и только если запись уже недействительна (истекла или отозвана). Действующие записи не удаляются ни при каком значении. Больше 0, иначе старт останавливается |
| `SigningCertificatePath` / `SigningCertificateBase64` / `SigningCertificatePassword` | — | Сертификат подписи токенов. Обязателен вне `Development` (`Path` или `Base64`) |
| `EncryptionCertificatePath` / `EncryptionCertificateBase64` / `EncryptionCertificatePassword` | — | Сертификат шифрования токенов. Обязателен вне `Development` (`Path` или `Base64`) |

Поведение зависит от окружения, и это важнее, чем кажется:

* **`Development`** — без явных сертификатов генерируются dev-ключи. Удобно локально, но каждый
  рестарт даёт новые ключи, и несколько инстансов их не разделяют.
* **Любое другое окружение** — dev-ключей нет: сервер **не стартует**, пока не заданы **оба**
  сертификата (подписи и шифрования). Отката на автогенерацию не существует.

Готовый Docker-образ запускается с `ASPNETCORE_ENVIRONMENT=Production`, то есть под второе
правило. Если вы видите сгенерированные dev-ключи в развёрнутой установке — значит процесс
поднят в `Development`, и это стоит исправить до выкатки.

### Clients

`Veriqa:OpenIddict:Clients` — массив приложений, которым разрешено выполнять вход:

| Ключ | Значение |
| - | - |
| `ClientId` | Идентификатор клиента. Уникален в пределах массива: две записи с одним `ClientId` останавливают старт с ошибкой валидации — иначе всё содержимое второй записи молча не применялось бы |
| `DisplayName` | Имя этого приложения в текстах входа, которые пользователь получает в канале и в письме, — им заполняется их слот `{app}`. Не задано или пусто — вместо него подставляется `ClientId`. На подтверждения, которые создаёт ваш сервер (`POST /api/transaction/confirmation`), не действует: там `{app}` — всегда `ClientId`. На странице входа не выводится: имя рядом с логотипом там задаёт `BrandName` |
| `AllowedRedirectUris` | Список точного совпадения; без wildcard, только абсолютные URI |
| `AllowedScopes` | Scope'ы, которые клиент может запрашивать. При непустом [`Veriqa:ScopesClaims:Scopes`](#veriqascopesclaims) — только scope'ы этого каталога и `openid`: scope вне него останавливает старт — приложение сужает каталог, а не расширяет |
| `AllowRefreshTokens` | Учитывается ли `offline_access` |
| `AllowClientCredentials` | Разрешён ли клиенту Client Credentials Grant — так ваш сервер аутентифицируется, чтобы создавать транзакции подтверждения (`POST /api/transaction/confirmation`) и читать их результат. По умолчанию выключено. Только для confidential-клиента (с `ClientSecret`): включение у public-клиента останавливает старт |
| `AllowIntrospection` | Разрешено ли клиенту вызывать эндпоинт интроспекции `/connect/introspect` (RFC 7662). По умолчанию выключено. Только для confidential-клиента (с `ClientSecret`): включение у public-клиента останавливает старт. Ответ несёт только метаданные, а клиент с этим разрешением видит `sub` и `client_id` токенов, выданных другим клиентам, — выдавайте его только своим confidential-клиентам. См. [Поддержку OIDC и OAuth 2.0](/docs/ru/reference/oidc-capabilities) |
| `AllowConfirmationTokenGrant` | Разрешено ли клиенту обменивать подтверждённую транзакцию подтверждения, которую он создал, на `id_token` подтвердившего (grant `urn:veriqa:params:oauth:grant-type:confirmation`). По умолчанию выключено. Это разрешение интегратора: ни один параметр запроса приложения раскрытие не расширяет, а claims не выходят за `AllowedScopes`. Требует `AllowClientCredentials` и `openid` в `AllowedScopes` — без любого из них старт останавливается. См. [Привязку с сервера](/docs/ru/scenarios/channel-linking#привязка-с-сервера-без-входа) |
| `AllowCollectedClaimsDeletion` | Разрешено ли клиенту удалять собранные claims субъекта через `DELETE /api/collected-claims` (эндпоинт есть, только когда зарегистрировано хранилище собранных claims). По умолчанию выключено. Удаляет персональные данные любого субъекта тенанта клиента, поэтому выдавайте его одному своему серверному клиенту; своего ограничения частоты у эндпоинта нет. Требует `AllowClientCredentials` — без него старт останавливается. См. [Хранилища](/docs/ru/guides/storage#удаление-по-sub) |
| `TenantId` | Тенант, которому принадлежит это приложение. Не задан или пуст — запись не называет тенанта, и транзакция создаётся без него: так работает self-hosted-установка с единственным тенантом. Значение читается один раз на запрос — каждым запросом, который знает клиента, но не имеет транзакции, чтобы взять тенанта с неё: вход и server-to-server-создание транзакции подтверждения записывают его на создаваемую транзакцию, и дальше он едет на ней самой; страница ответа `form_post`, выдаваемая уже после транзакции, читает его заново. Тенант — уровень резолюции конфигурационных ключей, а **не** граница доступа: изоляцию даёт `client_id`, стоящий выше тенанта |
| `RequirePhone` / `RequireEmail` | Требует ли это приложение `phone_number` / `email` — то же сокращение, что в [`Veriqa:ScopesClaims`](#veriqascopesclaims). Не задано — действует глобальное значение. Только строже: `false` здесь не снимает требование глобальной секции |
| `ClaimLevels` | Словарь «имя claim → уровень» (`IfAvailable`, `Optional`, `Required`), который ПОДНИМАЕТ уровень строки каталога этого claim — только для этого приложения; более низкий уровень ничего не меняет. Подъём claim, который Veriqa не может добрать (см. `Level` в [`Veriqa:ScopesClaims`](#veriqascopesclaims)), останавливает старт |
| `ClaimCompletionFormEnabled` | Показывает ли Veriqa пользователям этого приложения собственную форму добора claims. Не задано — действует `Veriqa:ClaimCompletion:FormEnabled`, и форма включена, если глобальная секция не говорит иного. `false` — недостающий claim, кроме `phone_number` и `email`, выдаётся, только если его дал канал, и вход никогда не блокирует. Выключайте для RP со своей формой добора профиля — см. [Keycloak](/docs/ru/integrations/apps#keycloak) |
| `ClaimCompletionFormSurface` | Где Veriqa показывает пользователям этого приложения форму добора claims: `Auto`, `InitiatingPage`, `CrossDevicePage` или `CrossDevicePageOnly`. Не задано — действует `Veriqa:ClaimCompletion:FormSurface`, `Auto`, если глобальная секция не говорит иного. `Auto` показывает форму на устройстве, где подтвердили вход, если вход подтверждён на странице подтверждения письма Email Pull или у учётки канала нет идентификатора для показа (Telegram без username), иначе — на странице входа. `InitiatingPage` — всегда страница входа. `CrossDevicePage` — устройство, где подтвердили вход: страница подтверждения письма или страница, которую открывает кнопка-ссылка канала в чате; если канал кнопку-ссылку не умеет, issuer — не абсолютный HTTPS-URL или кнопка не доставлена, форма переходит на страницу входа. `CrossDevicePageOnly` — то же без перехода: недостающий обязательный claim тогда завершает вход ошибкой `required_claim_unobtainable`, а необязательный пропускается |
| `ClaimCompletionEmailMethods` | Способы добора недостающего `email`, которые допускает это приложение: `Push`, `Code`, `MagicLink` и `Unverified`. Не задано — действует список тенанта, а без него `Veriqa:ClaimCompletion:EmailMethods` (`Push`, `Code`, `MagicLink`, если глобальная секция не говорит иного). Только сужение: способ, которого нет в списке уровнем выше, отбрасывается, порядок остаётся порядком списка выше. Первым предлагается первый доступный на странице способ, остальные — альтернативами: `Push` — только на устройстве, где подтвердили вход, и при приёме входящих писем Email-адаптера, `Code` и `MagicLink` — при отправке писем Email-адаптером, `Unverified` — всегда; адрес тогда не подтверждён, `email_verified` — `false`. Нет доступного способа — обязательный `email` не исполняется; если это приложение требует email, а на странице входа нет ни одного доступного способа, хост не стартует. Пустой список отклоняется — вместо него понижайте уровень требования email |
| `ClaimCompletionEmailLimits` | Лимиты писем добора `email`: `CodeAttempts` (попыток на код, 5), `MailsPerTransaction` (3), `MailsPerSubject` (на учётку канала за 24 часа, 10), `MailsPerAddress` (на адрес получателя за час, 5), `MailsPerDomain` (на домен получателя за час; `0` — выключен, по умолчанию). Не задано — действует значение тенанта или `Veriqa:ClaimCompletion:EmailLimits`. Только строже: большее значение здесь не снимает лимит уровня выше, а `0` не выключает доменный лимит, заданный выше |
| `CompatibilityQuirks` | Ключи квирков совместимости из закрытого набора — послабления формы запроса для этого клиента. Не задан по умолчанию; действует только при открытом гейте (ниже). См. [Совместимость с клиентами](/docs/ru/guides/client-compatibility) |
| `LogoUrl` | Логотип на странице входа этого приложения; как и в глобальной секции, показывается в любом пресете, кроме `Minimal`. Не задан — логотип берётся с уровня ниже; если ни логотипа, ни имени бренда нет ни на одном уровне, страница показывает собственный знак продукта |
| `BrandName` | Имя бренда рядом с логотипом на странице входа этого приложения; как и в глобальной секции, показывается в любом пресете, кроме `Minimal`. Не задано или пусто — имя берётся с уровня ниже. Резолвится само по себе, поэтому запись может задать имя, а логотип придёт с другого уровня |
| `FooterHtml` | Блок в подвале страницы входа этого приложения — ваша разметка внизу карточки. Не задан или пуст — блок берётся с уровня ниже: пустым значением здесь блок, заданный глобальной секцией, не погасить. Разметка вне допустимого формата выбрасывает эту запись клиента из эффективной конфигурации **целиком**, и остальные её настройки тогда резолвятся с уровня ниже — формат и исходы см. в [Блоке в подвале страницы входа](/docs/ru/guides/customization#блок-в-подвале-страницы-входа) |
| `HopInstructionHtml` | Инструкция, которую страница входа этого приложения показывает на месте выбора канала, когда пользователь в hop-режиме продолжил на телефоне, — ваша разметка вместо текста ядра. Не задана или пуста — инструкция берётся с уровня ниже. Формат и исход недопустимого значения — как у `FooterHtml`: запись клиента выбрасывается **целиком** — см. [Инструкцию после скана](/docs/ru/guides/customization#инструкция-после-скана) |
| `PrimaryColor` | Акцентный цвет страницы входа этого приложения, `#RRGGBB`; применяется только при `Preset: Branded`. Не задан — цвет берётся с уровня ниже |
| `LogoUrlByTheme` | Логотипы этого приложения для конкретной темы: карта `light`/`dark` → URL. Тема без своего значения берёт `LogoUrl` этой же записи |
| `PrimaryColorByTheme` | Акцентные цвета этого приложения для конкретной темы: карта `light`/`dark` → `#RRGGBB`. Тема без своего значения берёт `PrimaryColor` этой же записи |
| `CustomCssPath` / `CssSriHash` | Кастомная таблица стилей страницы входа этого приложения и её SRI-хеш. Резолвятся парой: запись, задавшая путь, владеет и хешем; путь не задан — оба берутся с уровня ниже |
| `CustomJsPath` / `JsSriHash` | Кастомный скрипт страницы входа этого приложения и его SRI-хеш — рисковый режим. Действует только при открытом гейте [`Veriqa:AuthPageDesign:CustomJsAllowApplicationOverride`](#veriqaauthpagedesign); при закрытом гейте скрипт берётся с уровня ниже, а в лог при старте пишется предупреждение. Резолвятся парой, как CSS |
| `QrCode` | QR-код страницы входа для этого клиента: те же ключи, что в секции [`Veriqa:AuthPageDesign:QrCode`](#veriqaauthpagedesignqrcode), каждый необязателен. Не задан — QR берётся с уровня ниже |

Рядом с массивом клиентов — гейт развёртывания:

| Ключ | Дефолт | Значение |
| - | - | - |
| `CompatibilityQuirksAllowApplicationOverride` | `false` | Пока закрыт, ни один квирк не действует, что бы ни стояло у клиентов |

## Veriqa:TransactionEngine

Жизненный цикл транзакции входа.

| Ключ | Дефолт | Значение |
| - | - | - |
| `TransactionTtlSeconds` | `300` | Сколько у пользователя есть на подтверждение |
| `ConfirmationFinalizationTimeoutSeconds` | `30` | Окно на финализацию после подтверждения |
| `CompletedRetentionSeconds` | `600` | Сколько мёртвая транзакция — завершённая, отказанная или истёкшая — остаётся читаемой до удаления |
| `CleanupIntervalSeconds` | `60` | Интервал зачистки истёкших транзакций |
| `CleanupBatchSize` | `100` | Строк за одну зачистку |
| `CleanupErrorRetryDelaySeconds` | `10` | Задержка повтора после неудачной зачистки |
| `MaxInMemoryEntries` | `10000` | Лимит для in-memory хранилища |
| `RejectBots` | `true` | Отвергать транзакции, начатые ботами |
| `RejectBotsAllowApplicationOverride` | `false` | Позволить приложению переопределить это решение |

### Store

| Ключ | Дефолт | Значение |
| - | - | - |
| `ConnectionString` | — | Задайте, чтобы переключиться с in-memory на EF Core |
| `Provider` | `PostgreSQL` | Реляционный провайдер для хранилища |

Про миграции и ретенцию — см. [Хранилища](/docs/ru/guides/storage).

## Veriqa:ScopesClaims

Какие scope'ы существуют, какие claim'ы они несут и какие claim'ы требуются для входа.

| Ключ | Дефолт | Значение |
| - | - | - |
| `Scopes` | — | Каталог scope'ов, каждый со своим `Name` и своими `Claims` (ключи ниже). Пусто — регистрируются собственные scope'ы продукта: `openid`, `profile`, `email`, `phone`, `offline_access`, `channel`, `avatar` |
| `RequirePhone` | `false` | Требовать `phone_number` — сокращение для `Level: Required` у этого claim. Приложение может потребовать его само (`RequirePhone` записи клиента), но снять требование не может. Действует на вход, запросивший scope `phone`: такому входу предлагаются только каналы, способные дать номер телефона, а если ни одного такого нет — `/connect/authorize` отвечает `400` `no_channel_for_required_phone` |
| `RequireEmail` | `false` | Требовать `email` — то же сокращение для email |

<Warning>
  Непустой `Scopes` **замещает** собственный набор scope'ов продукта целиком: `scopes_supported`
  discovery-документа перечисляет ровно scope'ы каталога (плюс `openid`, который регистрируется всегда), а
  запрос любого другого scope отклоняется с `invalid_scope`. Если ваши клиенты используют `offline_access`,
  `channel` или `avatar`, перечислите их в каталоге явно.
</Warning>

Ключи claim в `Claims`:

| Ключ | Дефолт | Значение |
| - | - | - |
| `Name` | — | Имя claim, как в токене. Один claim — одна строка. Claim стандартного scope (`name` у `profile`, `phone_number` у `phone`, `email` у `email`, …) может стоять только под этим scope |
| `Level` | `IfAvailable` | Уровень требования: `IfAvailable` — выдаётся, только если канал или хранилище его уже дали; `Optional` — пользователя спрашивают, он может отказаться; `Required` — без него вход не завершается. Уровень выше `IfAvailable` допустим только у claim, который Veriqa может добрать, — `phone_number`, `email` или строка с `Completion`; у любого другого claim он останавливает старт. Приложение может уровень поднять (`ClaimLevels` записи клиента), но не понизить |
| `Source` | `Channel` | `Channel` — выдаётся каналом; `External` — добавляется вашим claims mapper после Veriqa; `Collected` — собирается Veriqa: каналом не выдаётся, берётся только из хранилища или добором, поэтому требует `Completion` |
| `Completion` | — | Описание поля добора: `Label` — подпись поля, ключ локализации, не пустая; `MaxLength` — по умолчанию `256`, положительное, к датам не применяется; `Format` — `Text` или `Date`. Допустимо у claim с источником `Channel` и `Collected`. Останавливает старт у `External`-claim, у `phone_number` и `email` (у них свои пути) и у claim, которые ядро выпускает само: `sub`, `amr`, `auth_time`, `zoneinfo`, `updated_at`, `email_verified`, `phone_number_verified`, `channel_type`, `channel_user_id`, `is_bot`, `veriqa_channel`, `veriqa_email_mode` |

## Veriqa:AuthPageDesign

Внешний вид страницы входа — полный разбор в
[Конфигурации и кастомизации](/docs/ru/guides/customization).

| Ключ | Дефолт | Значение |
| - | - | - |
| `Preset` | `Default` | Пресет дизайна; `Branded` включает `PrimaryColor` |
| `Theme` | `Auto` | `Auto`, `Light` или `Dark` |
| `LogoUrl` | — | Ваш логотип, в любом пресете, кроме `Minimal`. Если не задан ни `LogoUrl`, ни `BrandName`, страница показывает собственный знак продукта |
| `BrandName` | — | Имя бренда рядом с логотипом, в любом пресете, кроме `Minimal`. В отличие от логотипа по теме не режется — имя одно на обе |
| `FooterHtml` | — | Блок в подвале страницы входа: ваша разметка (правовые ссылки, сноска, контакт) внизу карточки, во всех пресетах. Разметка вне допустимого формата останавливает старт — формат см. в [Блоке в подвале страницы входа](/docs/ru/guides/customization#блок-в-подвале-страницы-входа) |
| `HopInstructionHtml` | — | Инструкция, которую страница входа показывает на месте выбора канала, когда пользователь в hop-режиме продолжил на телефоне: ваша разметка в формате `FooterHtml`. Не задана — собственный текст ядра. Разметка вне допустимого формата останавливает старт — см. [Инструкцию после скана](/docs/ru/guides/customization#инструкция-после-скана) |
| `PrimaryColor` | — | Акцентный цвет, `#RRGGBB`; применяется только при `Preset: Branded` |
| `LogoUrlByTheme` | — | Логотип для конкретной темы: карта `light`/`dark` → URL. Тема без своего значения берёт `LogoUrl` |
| `PrimaryColorByTheme` | — | Акцентный цвет для конкретной темы: карта `light`/`dark` → `#RRGGBB`. Тема без своего значения берёт `PrimaryColor` |
| `CustomCssPath` / `CustomJsPath` | — | Внешние ассеты; кастомный путь CSS переопределяет пресет |
| `CssSriHash` / `JsSriHash` | — | Хеши Subresource Integrity для этих ассетов. Хеш резолвится вместе со своим путём — оба всегда приходят с одного уровня |
| `CustomJsAllowApplicationOverride` | `false` | Gate рискового режима: разрешает записи клиента (`Veriqa:OpenIddict:Clients`) задавать свой `CustomJsPath`/`JsSriHash`. Без него скрипт задаёт только эта секция |

### Veriqa:AuthPageDesign:QrCode

QR-код на странице входа: показывать ли его и в каком разрешении рисовать.

| Ключ | Дефолт | Значение |
| - | - | - |
| `ShowQrCode` | `DesktopOnly` | Показывать ли QR: `Always` — на любом устройстве; `DesktopOnly` — везде, кроме телефона, где вместо него кнопки каналов; `Never` — только кнопки каналов, на любом устройстве |
| `PixelsPerModule` | `6` | Разрешение QR — пикселей на модуль; допустимый диапазон `5`…`35` |
| `PixelsPerModuleByChannel` | — | Карта «тип канала → разрешение» **того же уровня**, читается раньше его `PixelsPerModule`; канала нет в карте — берётся `PixelsPerModule` того же уровня |

Секция задаётся на трёх уровнях владения, и побеждает ближайший заданный: запись `ui_config` →
клиент (`Veriqa:OpenIddict:Clients[].QrCode`) → эта глобальная секция. Показ QR и разрешение —
две разные настройки, и уровень выигрывает каждую отдельно: клиент, задавший только `ShowQrCode`,
оставляет разрешение глобальным.

Разрешение при этом — **одна** настройка, заданная по каналам: `PixelsPerModuleByChannel` и
`PixelsPerModule` — две записи одного и того же уровня, и уровень исчерпывает их обе, прежде чем
разрешение начнёт искаться уровнем ниже. Практическое следствие: клиент, задавший только
`PixelsPerModule`, выигрывает разрешение **для всех** каналов — карта по каналам из глобальной
секции до него не доходит. Чтобы у канала осталось своё разрешение, назовите его в карте того же
уровня, который задаёт `PixelsPerModule`.

Пример. Глобальная секция задаёт `PixelsPerModule: 6` и `PixelsPerModuleByChannel` с записью
`telegram: 14`, а клиент `app-client` — только `PixelsPerModule: 8`:

| Канал | Разрешение для `app-client` |
| - | - |
| `telegram` | `8` — карты у клиента нет, побеждает его `PixelsPerModule`, и карта глобальной секции уже не читается |
| `whatsapp` | `8` — по той же причине |

`PixelsPerModule` вне диапазона на уровне клиента или записи `ui_config` не применяется —
разрешение приходит с уровня ниже, старт не падает. Про такое значение сообщает **одно**
предупреждение в логе на снимок конфигурации: весь каталог значений (глобальная секция, записи
клиентов, записи `ui_config`) проверяется при старте и при каждой перезагрузке конфигурации, а не
на каждый запрос. В глобальной секции такое значение остаётся ошибкой развёртывания и
останавливает старт.

## Veriqa:Localization и Veriqa:ChannelDisplay

| Ключ | Дефолт | Значение |
| - | - | - |
| `Localization:DefaultLanguage` | `en` | Язык страницы входа, когда у пользователя нет предпочтения |
| `ChannelDisplay:Mode` | `Multichannel` | Как представлены каналы |
| `ChannelDisplay:ChannelOrder` | — | Явный порядок кнопок каналов |
| `ChannelDisplay:Hints` | — | Карта «тип канала → текст подсказки». Подсказка выводится в панели своего канала, поэтому при вкладках видна только у выбранного, а при [кнопках каналов](/docs/ru/guides/customization#кнопки-каналов) стоит под кнопкой своего канала. Ключ — тип канала (`whatsapp`, `telegram`, ваш SPI-канал), регистр не важен; канала нет в карте или текст пуст — подсказки нет |

## Veriqa:RateLimit

Per-endpoint лимиты, каждый — число разрешений за окно в секундах. Дефолты:

| Группа эндпоинтов | Разрешений / окно |
| - | - |
| `Authorize` | 10 / 60s |
| `Token` | 20 / 60s |
| `Callback` | 10 / 60s |
| `Polling` | 60 / 60s |
| `Webhook` | 100 / 60s |
| `SignalR` | 30 / 60s |
| `EmailStart` | 5 / 60s |
| `UserAuth` | 5 / 60s (`UserAuthQueueLimit` 0, `UserAuthMaxPartitions` 50000) |

Лимиты вебхуков заслуживают внимания при масштабировании: они применяются на инстанс, и агрессивно
ретраящий провайдер канала может их выбить.

## Veriqa:InitiatorContext

Контекст об устройстве, начавшем транзакцию, показываемый пользователю до подтверждения — см.
[Контекст инициатора](/docs/ru/concepts/initiator-context).

| Ключ | Дефолт | Значение |
| - | - | - |
| `Enabled` | `true` | Собирать ли контекст инициатора вообще |
| `CollectIpAddress` | `true` | Собирать IP-адрес |
| `CollectUserAgent` | `true` | Собирать user agent |
| `CollectGeoLocation` | `true` | Разрешать геолокацию |
| `StoreRawUserAgent` | `false` | Сохранять сырую UA-строку |
| `DisplayFields` | см. страницу концепции | Какие поля показывает сообщение подтверждения |
| `AnomalyDetection:Enabled` | `false` | Помечать необычный контекст входа |
| `GeoIp:Provider` / `GeoIp:DatabasePath` | offline | Источник GeoIP |

<Warning>
  Offline-путь GeoIP ожидает базу **GeoLite2**, которую Veriqa не поставляет. Скачайте её у MaxMind
  сами и примите их EULA — библиотека Apache-2.0, лицензия лежит на данных. Саму библиотеку тоже
  надо подключить: она едет сателлитным пакетом `Veriqa.Core.AuthServer.MaxMind`
  (`builder.Services.AddMaxMindGeoIp()`), в базовую поставку не входит.
</Warning>

## Veriqa:HopMode

Hop-режим кладёт в QR-код короткую одноразовую ссылку на ваш сервер Veriqa вместо ссылки канала. Две его
настройки задаются на весь узел и могут быть переопределены для отдельного канала:

| Ключ | Дефолт | Значение |
| - | - | - |
| `Veriqa:HopMode:Enabled` | `false` | Hop-режим включён для всех каналов |
| `Veriqa:HopMode:RedirectStyle` | `Interstitial` | Как hop-ссылка ведёт в канал: `Interstitial` или `Direct` |
| `Veriqa:Channels:{Channel}:HopEnabled` | не задан | Перекрывает `Veriqa:HopMode:Enabled` для этого канала |
| `Veriqa:Channels:{Channel}:HopRedirectStyle` | не задан | Перекрывает `Veriqa:HopMode:RedirectStyle` для этого канала |

Заданное значение канала побеждает значение узла; отсутствующее или пустое — наследует его. Остальные
ключи `HopMode.*` и их дефолты — в разделе [Ключи в каталоге объявлений](#ключи-в-каталоге-объявлений),
где у `HopMode.Enabled` и `HopMode.RedirectStyle` стоит «not declared»: их дефолты — значения выше.

## Инфраструктура

Плоские ключи, читаемые хостом, в основном актуальны при запуске более одного инстанса:

| Ключ | Значение |
| - | - |
| `Veriqa:SignalR:RedisConnectionString` | SignalR backplane; не задан — single-instance |
| `Veriqa:DataProtection:RedisConnectionString` | Ключи DataProtection в Redis |
| `Veriqa:DataProtection:KeysDirectory` | Ключи DataProtection на диске (общий том) |
| `Veriqa:Events:RabbitMq:ConnectionString` | Публикатор событий; не задан — публикации нет |
| `Veriqa:CollectedClaims:Store:Enabled` | Хранилище собранных claims, по умолчанию `false`: `true` — собранные Veriqa claims хранятся в базе хранилища транзакций; требует `Veriqa:TransactionEngine:Store:ConnectionString` — без неё хост не стартует. См. [Хранилища](/docs/ru/guides/storage#хранилище-собранных-claims) |
| `ForwardedHeaders:KnownProxies` / `:KnownIPNetworks` | Доверенные прокси за терминацией TLS |

Если не задан ни один вариант DataProtection, ключи живут в памяти и теряются при рестарте — нормально
для разработки, сломано для масштабируемого деплоя.

<Note>
  **Лицензирование Redis.** Redis server ≥ 7.4 поставляется под RSALv2/SSPL (8.x дополнительно
  предлагает AGPL). На лицензию Veriqa это не влияет, но если влияет на вашу политику — **Valkey**
  (BSD) является drop-in альтернативой.
</Note>

## Veriqa:Channels

Каждый канал живёт под `Veriqa:Channels:{Channel}` и активируется только с `"Enabled": true`. Полная
настройка с шагами на стороне провайдера — в [Настройке каналов](/docs/ru/guides/channels).

| Секция | Ключевые настройки |
| - | - |
| `Veriqa:Channels:Telegram` | `BotToken`, `UpdateMode`, `WebhookBaseUrl`, `WebhookSecretToken`, `BotUsername` |
| `Veriqa:Channels:Max` | `BotToken`, `UpdateMode`, `WebhookBaseUrl`, `WebhookSecretToken`, `BotPublicName` |
| `Veriqa:Channels:WhatsApp` | `Provider`, `BusinessPhoneNumber`, `MetaCloudApi:*`, `Twilio:*` |
| `Veriqa:Channels:Email` | `PublicBaseUrl`, `PullEnabled`, `PushEnabled`, `Outbound:*`, `Inbound:*` |

### InboundVerification — обязательный ключ каждой канальной секции

`Veriqa:Channels:{Channel}:InboundVerification` объявляет, **как проверяется подлинность входящего
события этого канала**. Значение выбирается из закрытого словаря:

| Значение | Смысл |
| - | - |
| `signature` | Подпись тела запроса ключом платформы (HMAC и подобное) |
| `shared_secret` | Общий секрет, предъявляемый входящим запросом (заголовок, токен) |
| `outbound_fetch` | Входящих запросов нет — Veriqa забирает события своим исходящим запросом |
| `none` | Подлинность входящего события не проверяется |

<Warning>
  **Ключ обязателен для каждого включённого канала.** Развёртывание, где включён канал, объявленный
  на уровне ядра (`"Enabled": true`), а значение не задано или не входит в словарь, **не
  стартует**: сообщение назовёт тип канала, адрес ключа и все четыре допустимых токена. Значение
  стоит проставить и в секциях выключенных каналов — тогда переключение `Enabled` в `true` не уронит
  старт. Стартовая проверка перебирает ровно тот набор каналов, из которого ядро строит список
  включённых: все четыре поставляемых канала в нём есть, а канал, подключённый через SPI одним
  вызовом `AddChannel`, в него не попадает — ключ в его секции тоже нужен, но без него старт не
  остановится, см. [Свой канальный адаптер](/docs/ru/guides/custom-channel-adapter).
</Warning>

Факт **объявляете вы**, а не адаптер и не ядро: Veriqa видит только то, что проверка выполнилась и
прошла, но не то, чем именно она была, — а вывод значения по типу канала означал бы, что сторонний
канал описать нечем. Значение для каждого встроенного канала и режима — в
[Настройке каналов](/docs/ru/guides/channels). Канал, забирающий обновления сам (`UpdateMode: Polling`), но
объявивший что-то кроме `outbound_fetch`, даёт `Warning` при старте — старт при этом продолжается.

Объявленное значение доезжает до записи журнала аудита отдельным атрибутом, поэтому по журналу видно,
чем было защищено подтверждение.

### OutcomeNotice — где появляется квитанция исхода

`Veriqa:Channels:OutcomeNotice:DisplayIntent` (ключ `Channels.OutcomeNotice.DisplayIntent`) говорит,
**как вы хотите видеть квитанцию терминального исхода** в переписке. Значение задаётся на трёх
уровнях — ядра, тенанта и приложения, — и каждый свободно перекрывает нижний: глобальная секция
задаёт намерение всей установки, тенант — своё, членом `OutcomeNoticeDisplayIntent` своей записи,
отдельное приложение — членом с тем же именем в своей записи OIDC-клиента. Запись `ui_config`
намерения не задаёт. Какой бы уровень ни ответил, значение действует на исход целиком —
подтверждение, отказ и истечение показываются одинаково.

| Значение | Чего просит |
| - | - |
| `ReplacePrompt` | Квитанция занимает место сообщения, в котором был показан вопрос: в переписке остаётся одно сообщение, а кнопки уходят вместе с текстом, которому принадлежали |
| `NewMessage` | Квитанция приходит отдельным сообщением; вопрос остаётся в переписке с неизменной формулировкой, а кнопки у него снимаются там, где платформа умеет это без переписывания текста |

Поставочное значение — `ReplacePrompt`: ровно то, что каналы делали до появления настройки, поэтому
развёртывание, которое ничего не задало, изменения не увидит. Нечитаемое значение читается как
незаданное и отвечает тем же `ReplacePrompt`; старт при этом не прерывается.

<Note>
  **Это намерение, а не гарантия.** Telegram и MAX его исполняют. WhatsApp всегда отправляет новое
  сообщение, а Email квитанцию исхода не показывает вовсе — так они устроены в сегодняшней поставке.
  Адаптер, не исполнивший намерение, не ошибается: ошибка не пишется в журнал, результат доставки не
  меняется. При `NewMessage` квитанция приходит отдельным сообщением, а кнопки вопроса снимаются тем
  же ходом — там, где платформа умеет сменить кнопки сообщения, не переписывая его текст;
  формулировка вопроса при этом не переписывается никогда. Неудача снятия доставку тоже не ломает —
  и если кнопка уцелела, повторное нажатие принесёт ещё одну квитанцию, потому что терминальную
  транзакцию переиграть нельзя.
</Note>

## Veriqa:MessageTemplates

Всё, что пользователь читает в мессенджере и в письме, — **сообщения**. Одно сообщение это **две**
настройки, а не одна текстовка: **контракт** объявляет, какие слоты у сообщения есть, а **шаблон**
несёт формулировки, которые этими слотами пользуются. Поэтому и адресов два — на сообщение, а не на
каждый текст:

| Настройка | Адрес на уровне `Core` |
| - | - |
| Контракт | `Veriqa:MessageTemplates:{вид}:[ByType:{тип транзакции}]:[ByAction:{тип действия}]:Contract` |
| Шаблон | `Veriqa:MessageTemplates:{вид}:[ByType:{тип транзакции}]:[ByAction:{тип действия}]:[BySurface:{поверхность}]:[ByChannel:{канал}]:Templates` |

Группы в квадратных скобках необязательны: формулировка, которая не сужается по оси, опускает её
группу целиком — так один адрес обслуживает все шаги подстановки, от самого специфичного
(`{вид, тип транзакции, тип действия, поверхность, канал}`) до самого общего (`{вид}`). Вид сообщения
из группы не выпадает никогда: «текст **какого** сообщения» — это и есть сам вопрос.

Одна группа не самостоятельна: `ByAction` читается **только внутри `ByType`**. Ни одна ступень
подстановки не называет тип действия без типа транзакции, поэтому формулировка по адресу
`{вид}:ByAction:{тип действия}` недостижима — хост на ней не стартует с сообщением, что адрес «narrowed
by a combination of axes no step of its ladder addresses». Тип действия подтверждения поэтому
формулируется по адресу `{вид}:ByType:confirmation:ByAction:{тип действия}`.

Виды, которые везёт поставка:

| Вид | Что это |
| - | - |
| `confirmation-prompt` | запрос подтверждения входа, который канал показывает пользователю |
| `sign-in-mail` | письмо входа Email-канала: тема и тело |
| `claim-code-mail` | письмо с кодом, подтверждающим адрес почты, который пользователь ввёл на веб-шаге добора claims |
| `claim-link-mail` | письмо со ссылкой, подтверждающей адрес почты, который пользователь ввёл на веб-шаге добора claims |
| `deeplink-prefill` | предзаполненное сообщение, которое пользователь отправляет сам (WhatsApp, Email Push) |
| `outcome-receipt-confirmed` | квитанция исхода: подтверждено — терминальный текст, которым транзакция отвечает на решение пользователя |
| `outcome-receipt-declined` | квитанция исхода: отклонено |
| `outcome-receipt-expired` | квитанция исхода: срок истёк |

Значение шаблона — **лестница ступеней** от самой полной к минимальной, одним массивом. Ступень это
либо строка (текст, годный любому приёмнику), либо структура
`{ "Subject": "…", "Html": "…", "Plain": "…" }`, где `Html` и `Plain` — **редакции** ступени: письмо
формируется одно и несёт обе части сразу. Каждая редакция необязательна, но ступень обязана заявить
хотя бы одну — иначе отказ на старте с указанием рода сообщения и номера ступени. Показывается первая
ступень, заявившая запрошенную редакцию и все слоты которой заполнены; поэтому две части одного письма
могут прийти с разных ступеней, а тема берётся у ступени, выбранной для `Html`. Редакцию, которую не
заявила ни одна ступень, письмо не несёт вовсе — так объявляется «письмо только в html». Минимальная
ступень обязана опираться только на гарантированные слоты — это проверяется на старте, на **каждом**
шаге и по **каждой редакции** отдельно. Шаг, объявивший лестницу, заменяет её целиком: ступени разных
шагов не смешиваются.

**Формулировка из поставки может стоять по более узкому адресу, чем та, что пишете вы.** Подстановка
идёт уровень за уровнем, и уровень исчерпывает все ступени адреса — от самой специфичной до самой
общей — прежде чем резолюция спустится на уровень ниже. Поставляемые объявления — это фолбэк уровня
`Core`, читаемый по **тем же** адресам; а `Veriqa:MessageTemplates` в вашем `appsettings.json` — это и
есть тот самый уровень. Поэтому ваше объявление по адресу шире поставочного не срабатывает вовсе:
первой отвечает более узкая ступень, а отвечает она из поставки. Ничего не падает и ничего не пишется
в лог — конфигурация корректна, её просто никто не спрашивает. (Запись выше ядра — другой уровень и
читается раньше, так что формулировка в записи клиента так не перекрывается, но и действует только на
этого клиента.)

Первыми с этим встречаются квитанции исхода: каждый из трёх видов везёт формулировку по голому
`{вид}` **и** более узкие — для входа:

| Поставочный адрес `outcome-receipt-confirmed` | Текст |
| - | - |
| `outcome-receipt-confirmed:Templates` | `Confirmed ✅` |
| `…:ByType:login:BySurface:in-channel-messenger:Templates` | `Sign-in confirmed ✅` |
| `…:ByType:login:BySurface:in-channel-plain:Templates` | `Sign-in confirmed` |
| `…:ByType:login:BySurface:email-confirm-page:Templates` | `Sign-in confirmed! You can close this tab.` |

`outcome-receipt-declined` поставляется по тем же четырём адресам; `outcome-receipt-expired` — по этим
четырём плюс ступень на `ByType:login`. Квитанция **входа**, показанная в мессенджере, спрашивает про
`{вид, login, in-channel-messenger}` — и про канал, по которому ни одна поставочная квитанция не
сужается, — поэтому переобъявление `outcome-receipt-confirmed:Templates` там не меняет ничего.
Формулируйте по тому же адресу, который занят поставкой:

```json appsettings.json theme={null}
{
  "Veriqa": {
    "MessageTemplates": {
      "outcome-receipt-confirmed": {
        "Contract": {
          "Slots": [ { "Name": "app", "Type": "string", "Source": "ServerInitiatorContext" } ]
        },
        "ByType": {
          "login": {
            "BySurface": {
              "in-channel-messenger": {
                "Templates": [ "Signed in to {app} ✅", "Signed in ✅" ]
              }
            }
          }
        }
      }
    }
  }
}
```

Контракт остаётся в корне вида — он сужается по типу транзакции и типу действия и никогда по
поверхности, — а лестница заканчивается ступенью без слотов: ни один слот квитанции не гарантирован
(см. ниже).

Квитанция **подтверждения** — уже другой адрес: под `ByType:confirmation` поставка не объявляет
ничего, поэтому там отвечает как раз широкое объявление по `{вид}`, и переформулировка работает как
написана. Поверхности, которыми адресуется квитанция: `in-channel-messenger` (канал, который рисует
эмодзи, — мессенджеры, на которые продукт везёт адаптер, и любой канал, заявивший это о себе),
`in-channel-plain` (канал, который не рисует), `email-confirm-page` (страница подтверждения
Email-канала), `core-page` (страница подтверждения, которую Veriqa отдаёт сама) и `interaction-page`
(страница, на которой остаётся пользователь при подтверждении без браузерного колбэка).

**Квитанции исхода — только простой текст.** Три вида `outcome-receipt-*` поставляются одной
редакцией — `Plain`, и общее правило «редакцию, которую не заявила ни одна ступень, письмо не несёт»
к ним не относится: у квитанции часть одна, и не показать её значит не ответить пользователю.
Поэтому лестница квитанции, переопределённая **только** редакцией `Html`, не даёт текста **вовсе** —
это ошибка установки, а не другой вид текста: рендер отказывает и называет вид сообщения, подмены
другой редакцией не происходит. Экранирование квитанции при вставке в страницу делает сама страница,
так что простой текст у формулировки ничего не отнимает.

**Квитанция может назвать, что подтверждали.** Поставляемый контракт трёх видов квитанций слотов не
объявляет, поэтому поставляемые квитанции о действии ничего не говорят. Объявите caller-слоты в
контракте квитанции на своём уровне — под `ByType:confirmation:ByAction:{action type}`, чтобы
формулировать её по действию, — и квитанция заполнится из `slot_values`, с которыми создавалось
подтверждение, по тем же именам. Caller-слот квитанции указывает `MaxLength`, как и в вопросе:

```json theme={null}
"outcome-receipt-confirmed": {
  "ByType": {
    "confirmation": {
      "ByAction": {
        "purchase-title": {
          "Contract": {
            "Slots": [ { "Name": "title", "Type": "string", "MaxLength": 64 } ]
          },
          "Templates": [ "Purchase confirmed: “{title}” ✅", "Purchase confirmed ✅" ]
        }
      }
    }
  }
}
```

Значение, которого у транзакции нет (вход, подтверждение, созданное без него), исключает
ступень, которая его использует, и отвечает следующая — поэтому минимальная ступень квитанции не
считает caller-слот гарантированным, даже помеченный `Required`. Квитанция на решение из канала,
которому вопрос не показывали, caller-значений не получает, как и квитанция на странице входа в
подтверждение, если `InteractionPage` не входит в `ConfirmationSubjectDisplay.Surfaces`. Server-слоты,
описанные ниже, заполняются и там, кроме `outcome_at` на странице входа.

**Квитанция может нести и то, что продукт знает о транзакции.** Её контракт может объявить
server-слоты `app`, `browser`, `os`, `region` (тип `string`, `Source: ServerInitiatorContext`) и
`outcome_at` (тип `datetime`, `Source: ServerSystem`); caller-значение их не заполняет. Объявление одного из этих пяти имён с `Source: Caller` при старте не отклоняется, но слота вам не отдаёт: `app` всегда принадлежит продукту, а остальные четыре — ваши только на тех ветках, где у продукта своего значения нет. Возьмите собственное имя. `app` называет
приложение: `client_id` у подтверждения, отображаемое имя клиента (или его `client_id`) у входа.
`browser`, `os` и `region` берутся из контекста инициатора, поэтому есть только у входа, и подчиняются
тем же `Enabled` и `DisplayFields` раздела `Veriqa:InitiatorContext`, что и промпт входа. `outcome_at` —
момент решения, а у истёкшей транзакции — её срок; страница входа формулирует квитанции до исхода,
поэтому там значения у него нет. Ни один из этих слотов в квитанции не гарантирован,
даже помеченный `Guaranteed`, поэтому минимальная ступень на них опираться не может.

Уровни у настроек разные. Контракт резолвится с `Tenant`, `Application` и
`Core`; шаблон — оттуда же **плюс** запись `ui_config`. Так запись, которую выбирает параметр
запроса, может выбрать, какую из ваших формулировок увидит пользователь, но не может ни расширить
список слотов, ни завести собственный тип действия.

<Note>
  **Уровень `Tenant`.** Ридер уровня `Tenant` в поставке не регистрируется — его пишет интегратор.
  Без него резолюция идёт на `Application`/`Core`.
</Note>

**Уровень `Application` — это запись клиента.** Выше ядра адрес отсчитывается от записи уровня, а
запись `Application` — это запись клиента в `Veriqa:OpenIddict:Clients`: контракт сообщения для одного
приложения лежит по адресу `Veriqa:OpenIddict:Clients:[n]:MessageTemplates:{kind}:…:Contract`.

**Action type подтверждений, которые создаёт ваш сервер,** — самостоятельные виды сообщений, и
объявляются они **только выше ядра** — в записи клиента, как `MessageTemplates:{action type}` с
`Contract` и `Templates`. Вид, видимый только в секции хоста `Veriqa:MessageTemplates`,
`POST /api/transaction/confirmation` отклоняет с `action_type_unknown`. Полный пример с запросом и
ответом: [API серверного подтверждения](/docs/ru/reference/confirmation-api).

**Ось `channel` — уточнение поверхности**, поэтому она самая специфичная и без `surface` не
встречается. Поставка объявляет по ней два значения — обе формулировки предзаполненного сообщения
под `deeplink-prefill:BySurface:in-channel:ByChannel:whatsapp` и `…:ByChannel:email`; у остальных
видов значений по этой оси нет, и шаг без канала отвечает за все каналы сразу. Спрашивать ключ об
оси, которой он не объявляет, — ошибка.

## Health-эндпоинты

| Эндпоинт | Назначение |
| - | - |
| `/health/live` | Liveness — процесс поднят |
| `/health/ready` | Readiness — настроенные зависимости отвечают |

Readiness регистрирует по проверке на каждую настроенную зависимость, поэтому отражает ровно то, что
вы подключили: нет настроенной БД — нет проверки БД.

## Ключи в каталоге объявлений

Таблица ниже строится из **каталога объявлений** ключей: имя, тип значения, уровни, измерения и
адрес на каждом уровне. Остальные ключи описаны в секциях выше.

Ниже — ключи, которыми владеет **Veriqa**. Тот же каталог открыт и тому, что поставляете вы: своё
расширение объявляет ключи на уровнях ядра, приложения и `ui_config` через тот же публичный API, и
его объявления попадают в эту схему на старте — см.
[Свои ключи конфигурации](/docs/ru/guides/config-keys).

Колонка «Дефолт» печатает то, что объявил владелец ключа: чем отвечает уровень ядра, когда его
запись ничего не заявляет. Ключ, такого ответа не объявивший, назван так прямо — его дефолты ищите в
секциях выше. «Объявлен, значение не печатается» стоит там, где ответ объявлен, а текста у него нет:
объявленный ответ равен `null`, либо ключ объявлен секретным — схема секретного ключа несёт факт
ответа и никогда его текст. Значения, которое поставила установка, в таблице нет ни в одной колонке:
здесь напечатано объявление, а не резолюция.

| Ключ | Тип значения | Уровни | Измерения | Адрес по уровням | Дефолт | Секрет |
| - | - | - | - | - | - | - |
| `AuthPage.Instruction` | `string` | UiConfig | — | UiConfig: `instruction` | не объявлен | нет |
| `AuthPage.Title` | `string` | UiConfig | — | UiConfig: `title` | не объявлен | нет |
| `AuthPageDesign.BrandName` | `string` | UiConfig · Application · Tenant · Core | — | UiConfig: `brand_name`<br />Application: `BrandName`<br />Tenant: `BrandName`<br />Core: `Veriqa:AuthPageDesign:BrandName` | не объявлен | нет |
| `AuthPageDesign.CustomCss` | `CorePageCustomResource` | UiConfig · Application · Core | — | UiConfig: `custom_css_path`<br />Application: `CustomCssPath`<br />Core: `Veriqa:AuthPageDesign:CustomCssPath` | не объявлен | нет |
| `AuthPageDesign.CustomJs` | `CorePageCustomResource` | Application (через гейт) · Core | — | Application: `CustomJsPath`<br />Core: `Veriqa:AuthPageDesign:CustomJsPath` | не объявлен | нет |
| `AuthPageDesign.FooterHtml` | `string` | Application · Tenant · Core | — | Application: `FooterHtml`<br />Tenant: `FooterHtml`<br />Core: `Veriqa:AuthPageDesign:FooterHtml` | не объявлен | нет |
| `AuthPageDesign.HopInstructionHtml` | `string` | Application · Tenant · Core | — | Application: `HopInstructionHtml`<br />Tenant: `HopInstructionHtml`<br />Core: `Veriqa:AuthPageDesign:HopInstructionHtml` | не объявлен | нет |
| `AuthPageDesign.LogoUrl` | `string` | UiConfig · Application · Tenant · Core | `theme` | UiConfig: `logo_url`<br />Application: `LogoUrl`<br />Tenant: `LogoUrl`<br />Core: `Veriqa:AuthPageDesign:LogoUrl` | не объявлен | нет |
| `AuthPageDesign.Preset` | `DesignPreset` | UiConfig · Core | — | UiConfig: `preset`<br />Core: `Veriqa:AuthPageDesign:Preset` | `Default` | нет |
| `AuthPageDesign.PrimaryColor` | `string` | UiConfig · Application · Core | `theme` | UiConfig: `primary_color`<br />Application: `PrimaryColor`<br />Core: `Veriqa:AuthPageDesign:PrimaryColor` | не объявлен | нет |
| `AuthPageDesign.QrCode.PixelsPerModule` | `int` | UiConfig · Application · Tenant · Core | `channel` | UiConfig: `qr_code:pixels_per_module`<br />Application: `QrCode:PixelsPerModule`<br />Tenant: `QrCode:PixelsPerModule`<br />Core: `Veriqa:AuthPageDesign:QrCode:PixelsPerModule` | не объявлен | нет |
| `AuthPageDesign.QrCode.ShowQrCode` | `QrCodeVisibility` | UiConfig · Application · Tenant · Core | — | UiConfig: `qr_code:show_qr_code`<br />Application: `QrCode:ShowQrCode`<br />Tenant: `QrCode:ShowQrCode`<br />Core: `Veriqa:AuthPageDesign:QrCode:ShowQrCode` | `DesktopOnly` | нет |
| `AuthPageDesign.SignalRClientPath` | `string` | UiConfig · Core | — | UiConfig: `signalr_client_path`<br />Core: `Veriqa:AuthPageDesign:SignalRClientPath` | не объявлен | нет |
| `AuthPageDesign.Theme` | `AuthPageTheme` | UiConfig · Core | — | UiConfig: `theme`<br />Core: `Veriqa:AuthPageDesign:Theme` | `Auto` | нет |
| `ChannelDisplay.Channels` | `IReadOnlyList`1\` | UiConfig | — | UiConfig: `channels` | не объявлен | нет |
| `ChannelDisplay.Hints` | `string` | Core | `channel` | Core: `Veriqa:ChannelDisplay:Hints:{channel}` | не объявлен | нет |
| `ChannelDisplay.Mode` | `ChannelDisplayMode` | UiConfig · Core | — | UiConfig: `channel_display_mode`<br />Core: `Veriqa:ChannelDisplay:Mode` | `Multichannel` | нет |
| `Channels.Enabled` | `IReadOnlyCollection`1\` | Tenant · Core | — | Tenant: без пути<br />Core: без пути | не объявлен | нет |
| `Channels.Http.AdapterSettings` | `string` | Tenant · Core | `channel` | Tenant: без пути<br />Core: `Veriqa:Channels:{channel}:Http:AdapterSettings` | не объявлен | да |
| `Channels.Http.AllowInsecureHttp` | `bool` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:AllowInsecureHttp` | `false` | нет |
| `Channels.Http.BaseUrl` | `string` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:BaseUrl` | не объявлен | нет |
| `Channels.Http.Capabilities.DeliversOutcomeNotice` | `bool` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:Capabilities:DeliversOutcomeNotice` | `false` | нет |
| `Channels.Http.Capabilities.ProvidesRecipientLocale` | `bool` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:Capabilities:ProvidesRecipientLocale` | `false` | нет |
| `Channels.Http.Capabilities.RendersEmoji` | `bool?` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:Capabilities:RendersEmoji` | не объявлен | нет |
| `Channels.Http.Capabilities.SupportedMessageKinds` | `IReadOnlyList`1\` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:Capabilities:SupportedMessageKinds` | `[plain_text]` | нет |
| `Channels.Http.Capabilities.SupportsInChannelConfirmation` | `bool` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:Capabilities:SupportsInChannelConfirmation` | `false` | нет |
| `Channels.Http.Capabilities.SupportsMessageUpdate` | `bool` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:Capabilities:SupportsMessageUpdate` | `false` | нет |
| `Channels.Http.DeepLinkSchemes` | `IReadOnlyList`1\` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:DeepLinkSchemes` | `[]` | нет |
| `Channels.Http.DeepLinkTimeout` | `TimeSpan` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:DeepLinkTimeout` | `00:00:02` | нет |
| `Channels.Http.DisplayName` | `string` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:DisplayName` | не объявлен | нет |
| `Channels.Http.ErrorMessageText` | `string` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:ErrorMessageText` | `Something went wrong. Go back to the sign-in page and start again.` | нет |
| `Channels.Http.IconSvgPath` | `string` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:IconSvgPath` | не объявлен | нет |
| `Channels.Http.OperationTimeout` | `TimeSpan` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:OperationTimeout` | `00:00:10` | нет |
| `Channels.Http.PreviousTransportSecret` | `string` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:PreviousTransportSecret` | не объявлен | да |
| `Channels.Http.RetryBudget` | `TimeSpan` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:RetryBudget` | `00:00:20` | нет |
| `Channels.Http.StaleLinkReplyText` | `string` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:StaleLinkReplyText` | не объявлен | нет |
| `Channels.Http.Tenant` | `string` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:Tenant` | не объявлен | нет |
| `Channels.Http.TransportSecret` | `string` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:TransportSecret` | не объявлен | да |
| `Channels.Http.UnaddressedReplyText` | `string` | Core | `channel` | Core: `Veriqa:Channels:{channel}:Http:UnaddressedReplyText` | не объявлен | нет |
| `Channels.InboundVerification` | `ChannelInboundVerification?` | Tenant · Core | `channel` | Tenant: без пути<br />Core: `Veriqa:Channels:{channel}:InboundVerification` | не объявлен | нет |
| `Channels.OutcomeNotice.DisplayIntent` | `OutcomeNoticeDisplayIntent` | Application · Tenant · Core | — | Application: `OutcomeNoticeDisplayIntent`<br />Tenant: `OutcomeNoticeDisplayIntent`<br />Core: `Veriqa:Channels:OutcomeNotice:DisplayIntent` | `ReplacePrompt` | нет |
| `Channels.email.Credentials` | `EmailTenantCredentials` | Tenant · Core | — | Tenant: без пути<br />Core: без пути | не объявлен | да |
| `Channels.max.Credentials` | `MaxTenantCredentials` | Tenant · Core | — | Tenant: без пути<br />Core: без пути | не объявлен | да |
| `Channels.telegram.Credentials` | `TelegramTenantCredentials` | Tenant · Core | — | Tenant: без пути<br />Core: без пути | не объявлен | да |
| `Channels.whatsapp.Credentials` | `WhatsAppTenantCredentials` | Tenant · Core | — | Tenant: без пути<br />Core: без пути | не объявлен | да |
| `ClaimCompletion.CollectedClaimsRetentionDays` | `int` | Tenant · Core | — | Tenant: `ClaimCompletionCollectedClaimsRetentionDays`<br />Core: `Veriqa:ClaimCompletion:CollectedClaims:RetentionDays` | `0` | нет |
| `ClaimCompletion.EmailCodeAttempts` | `int` | Application · Tenant · Core | — | Application: `ClaimCompletionEmailLimits:CodeAttempts`<br />Tenant: `ClaimCompletionEmailLimits:CodeAttempts`<br />Core: `Veriqa:ClaimCompletion:EmailLimits:CodeAttempts` | `5` | нет |
| `ClaimCompletion.EmailMailsPerAddress` | `int` | Application · Tenant · Core | — | Application: `ClaimCompletionEmailLimits:MailsPerAddress`<br />Tenant: `ClaimCompletionEmailLimits:MailsPerAddress`<br />Core: `Veriqa:ClaimCompletion:EmailLimits:MailsPerAddress` | `5` | нет |
| `ClaimCompletion.EmailMailsPerDomain` | `int` | Application · Tenant · Core | — | Application: `ClaimCompletionEmailLimits:MailsPerDomain`<br />Tenant: `ClaimCompletionEmailLimits:MailsPerDomain`<br />Core: `Veriqa:ClaimCompletion:EmailLimits:MailsPerDomain` | `0` | нет |
| `ClaimCompletion.EmailMailsPerSubject` | `int` | Application · Tenant · Core | — | Application: `ClaimCompletionEmailLimits:MailsPerSubject`<br />Tenant: `ClaimCompletionEmailLimits:MailsPerSubject`<br />Core: `Veriqa:ClaimCompletion:EmailLimits:MailsPerSubject` | `10` | нет |
| `ClaimCompletion.EmailMailsPerTransaction` | `int` | Application · Tenant · Core | — | Application: `ClaimCompletionEmailLimits:MailsPerTransaction`<br />Tenant: `ClaimCompletionEmailLimits:MailsPerTransaction`<br />Core: `Veriqa:ClaimCompletion:EmailLimits:MailsPerTransaction` | `3` | нет |
| `ClaimCompletion.EmailMethods` | `IReadOnlyList`1\` | Application · Tenant · Core | — | Application: `ClaimCompletionEmailMethods`<br />Tenant: `ClaimCompletionEmailMethods`<br />Core: `Veriqa:ClaimCompletion:EmailMethods` | `[Push, Code, MagicLink]` | нет |
| `ClaimCompletion.FormEnabled` | `bool` | Application · Core | — | Application: `ClaimCompletionFormEnabled`<br />Core: `Veriqa:ClaimCompletion:FormEnabled` | `true` | нет |
| `ClaimCompletion.FormSurface` | `ClaimFormSurface` | Application · Core | — | Application: `ClaimCompletionFormSurface`<br />Core: `Veriqa:ClaimCompletion:FormSurface` | `Auto` | нет |
| `ClaimCompletion.PhoneOptionalWindow` | `TimeSpan` | Application · Core | — | Application: `ClaimCompletionPhoneOptionalWindow`<br />Core: `Veriqa:ClaimCompletion:PhoneOptionalWindow` | `00:01:00` | нет |
| `Client.InitiateLoginUri` | `string` | Application | — | Application: `InitiateLoginUri` | не объявлен | нет |
| `ConfirmationSubjectDisplay.Surfaces` | `IReadOnlyCollection`1\` | Application · Tenant · Core | — | Application: `ConfirmationSubjectDisplaySurfaces`<br />Tenant: `ConfirmationSubjectDisplaySurfaces`<br />Core: `Veriqa:ConfirmationSubjectDisplay:Surfaces` | `[]` | нет |
| `HopMode.AcceptClientSignals` | `bool` | Tenant · Core | — | Tenant: без пути<br />Core: `Veriqa:HopMode:AcceptClientSignals` | `true` | нет |
| `HopMode.CollectFingerprint` | `bool` | Tenant · Core | — | Tenant: без пути<br />Core: `Veriqa:HopMode:CollectFingerprint` | `false` | нет |
| `HopMode.CollectGeoLocation` | `bool` | Tenant · Core | — | Tenant: без пути<br />Core: `Veriqa:HopMode:CollectGeoLocation` | `false` | нет |
| `HopMode.CollectIpAddress` | `bool` | Tenant · Core | — | Tenant: без пути<br />Core: `Veriqa:HopMode:CollectIpAddress` | `true` | нет |
| `HopMode.CollectScannerContext` | `bool` | Tenant · Core | — | Tenant: без пути<br />Core: `Veriqa:HopMode:CollectScannerContext` | `true` | нет |
| `HopMode.CollectUserAgent` | `bool` | Tenant · Core | — | Tenant: без пути<br />Core: `Veriqa:HopMode:CollectUserAgent` | `true` | нет |
| `HopMode.EmitScannedStatus` | `bool` | Tenant · Core | — | Tenant: без пути<br />Core: `Veriqa:HopMode:EmitScannedStatus` | `true` | нет |
| `HopMode.Enabled` | `bool` | Application · Tenant · Core | `channel` | Application: без пути<br />Tenant: без пути<br />Core: `Veriqa:HopMode:Enabled` | не объявлен | нет |
| `HopMode.HopPath` | `string` | Core | — | Core: `Veriqa:HopMode:HopPath` | `/r` | нет |
| `HopMode.QrMode` | `HopQrMode` | Application · Tenant · Core | — | Application: без пути<br />Tenant: без пути<br />Core: `Veriqa:HopMode:QrMode` | `PerChannel` | нет |
| `HopMode.RedirectStyle` | `HopRedirectStyle` | Application · Tenant · Core | `channel` | Application: без пути<br />Tenant: без пути<br />Core: `Veriqa:HopMode:RedirectStyle` | не объявлен | нет |
| `HopMode.StoreRawUserAgent` | `bool` | Tenant · Core | — | Tenant: без пути<br />Core: `Veriqa:HopMode:StoreRawUserAgent` | `false` | нет |
| `HopMode.TokenLifetime` | `TimeSpan?` | Application · Tenant · Core | — | Application: без пути<br />Tenant: без пути<br />Core: `Veriqa:HopMode:TokenLifetime` | объявлен, значение не печатается | нет |
| `HopMode.WarnOnRepeatScan` | `bool` | Tenant · Core | — | Tenant: без пути<br />Core: `Veriqa:HopMode:WarnOnRepeatScan` | `true` | нет |
| `IdentityMatch.ComparableTypes` | `IReadOnlyList`1\` | Application · Tenant · Core | — | Application: `IdentityMatchComparableTypes`<br />Tenant: `IdentityMatchComparableTypes`<br />Core: `Veriqa:IdentityMatch:ComparableTypes` | `[]` | нет |
| `InitiatorContext.DisplayFields` | `IReadOnlyList`1\` | Application · Core | — | Application: `InitiatorContextDisplayFields`<br />Core: `Veriqa:InitiatorContext:DisplayFields` | `[application, browser, os, region]` | нет |
| `InitiatorContext.Enabled` | `bool` | Application · Core | — | Application: `InitiatorContextEnabled`<br />Core: `Veriqa:InitiatorContext:Enabled` | `true` | нет |
| `Logging.Mode` | `LoggingMode` | Tenant · Core | — | Tenant: `Logging:Mode` (из имени ключа)<br />Core: `Veriqa:Logging:Mode` | `System` | нет |
| `Logging.RetentionDays` | `int` | Core | — | Core: `Veriqa:Logging:RetentionDays` | `90` | нет |
| `LoginConfirmation.Mode` | `LoginConfirmationMode` | Application · Tenant · Core | — | Application: `LoginConfirmationMode`<br />Tenant: `LoginConfirmationMode`<br />Core: `Veriqa:LoginConfirmation:Mode` | `ChannelDefault` | нет |
| `MessageTemplates.Contract` | `MessageContract` | Application · Tenant · Core | `kind` · `transaction_type` · `action_type` | Application: `MessageTemplates:{kind}:[ByType:{transaction_type}]:[ByAction:{action_type}]:Contract`<br />Tenant: `MessageTemplates:{kind}:[ByType:{transaction_type}]:[ByAction:{action_type}]:Contract`<br />Core: `Veriqa:MessageTemplates:{kind}:[ByType:{transaction_type}]:[ByAction:{action_type}]:Contract` | не объявлен | нет |
| `MessageTemplates.Template` | `MessageTemplateVariant[]` | UiConfig · Application · Tenant · Core | `kind` · `transaction_type` · `action_type` · `surface` · `channel` | UiConfig: `MessageTemplates:{kind}:[ByType:{transaction_type}]:[ByAction:{action_type}]:[BySurface:{surface}]:[ByChannel:{channel}]:Templates`<br />Application: `MessageTemplates:{kind}:[ByType:{transaction_type}]:[ByAction:{action_type}]:[BySurface:{surface}]:[ByChannel:{channel}]:Templates`<br />Tenant: `MessageTemplates:{kind}:[ByType:{transaction_type}]:[ByAction:{action_type}]:[BySurface:{surface}]:[ByChannel:{channel}]:Templates`<br />Core: `Veriqa:MessageTemplates:{kind}:[ByType:{transaction_type}]:[ByAction:{action_type}]:[BySurface:{surface}]:[ByChannel:{channel}]:Templates` | не объявлен | нет |
| `OidcServer.AccessTokenLifetimeSeconds` | `int` | Core | — | Core: `Veriqa:OpenIddict:Server:AccessTokenLifetimeSeconds` | `3600` | нет |
| `OidcServer.AuthorizationCodeLifetimeSeconds` | `int` | Core | — | Core: `Veriqa:OpenIddict:Server:AuthorizationCodeLifetimeSeconds` | `300` | нет |
| `OidcServer.EnableRefreshTokenRotation` | `bool` | Core | — | Core: `Veriqa:OpenIddict:Server:EnableRefreshTokenRotation` | `true` | нет |
| `OidcServer.EnableRevocation` | `bool` | Core | — | Core: `Veriqa:OpenIddict:Server:EnableRevocation` | `true` | нет |
| `OidcServer.Issuer` | `string` | Core | — | Core: `Veriqa:OpenIddict:Server:Issuer` | объявлен, значение не печатается | нет |
| `OidcServer.RefreshTokenLifetimeSeconds` | `int` | Core | — | Core: `Veriqa:OpenIddict:Server:RefreshTokenLifetimeSeconds` | `1209600` | нет |
| `OpenIddict.ClientCompatibilityQuirks` | `IReadOnlyList`1\` | Application · Core | — | Application: `CompatibilityQuirks`<br />Core: `Veriqa:OpenIddict:CompatibilityQuirksAllowApplicationOverride` | не объявлен | нет |
| `RateLimit.AuthorizePermitLimit` | `int` | Core | — | Core: `Veriqa:RateLimit:AuthorizePermitLimit` | `10` | нет |
| `RateLimit.AuthorizeWindowSeconds` | `int` | Core | — | Core: `Veriqa:RateLimit:AuthorizeWindowSeconds` | `60` | нет |
| `RateLimit.CallbackPermitLimit` | `int` | Core | — | Core: `Veriqa:RateLimit:CallbackPermitLimit` | `10` | нет |
| `RateLimit.CallbackWindowSeconds` | `int` | Core | — | Core: `Veriqa:RateLimit:CallbackWindowSeconds` | `60` | нет |
| `RateLimit.ConfirmationCreatePermitLimit` | `int` | Core | — | Core: `Veriqa:RateLimit:ConfirmationCreatePermitLimit` | `60` | нет |
| `RateLimit.ConfirmationCreateWindowSeconds` | `int` | Core | — | Core: `Veriqa:RateLimit:ConfirmationCreateWindowSeconds` | `60` | нет |
| `RateLimit.ConfirmationPagesPermitLimit` | `int` | Core | — | Core: `Veriqa:RateLimit:ConfirmationPagesPermitLimit` | `30` | нет |
| `RateLimit.ConfirmationPagesWindowSeconds` | `int` | Core | — | Core: `Veriqa:RateLimit:ConfirmationPagesWindowSeconds` | `60` | нет |
| `RateLimit.ConfirmationResultPermitLimit` | `int` | Core | — | Core: `Veriqa:RateLimit:ConfirmationResultPermitLimit` | `60` | нет |
| `RateLimit.ConfirmationResultWindowSeconds` | `int` | Core | — | Core: `Veriqa:RateLimit:ConfirmationResultWindowSeconds` | `60` | нет |
| `RateLimit.EmailStartPermitLimit` | `int` | Core | — | Core: `Veriqa:RateLimit:EmailStartPermitLimit` | `5` | нет |
| `RateLimit.EmailStartWindowSeconds` | `int` | Core | — | Core: `Veriqa:RateLimit:EmailStartWindowSeconds` | `60` | нет |
| `RateLimit.HopResolvePermitLimit` | `int` | Core | — | Core: `Veriqa:RateLimit:HopResolvePermitLimit` | `30` | нет |
| `RateLimit.HopResolveWindowSeconds` | `int` | Core | — | Core: `Veriqa:RateLimit:HopResolveWindowSeconds` | `60` | нет |
| `RateLimit.HopSignalPermitLimit` | `int` | Core | — | Core: `Veriqa:RateLimit:HopSignalPermitLimit` | `60` | нет |
| `RateLimit.HopSignalWindowSeconds` | `int` | Core | — | Core: `Veriqa:RateLimit:HopSignalWindowSeconds` | `60` | нет |
| `RateLimit.PollingPermitLimit` | `int` | Core | — | Core: `Veriqa:RateLimit:PollingPermitLimit` | `60` | нет |
| `RateLimit.PollingWindowSeconds` | `int` | Core | — | Core: `Veriqa:RateLimit:PollingWindowSeconds` | `60` | нет |
| `RateLimit.SignalRPermitLimit` | `int` | Core | — | Core: `Veriqa:RateLimit:SignalRPermitLimit` | `30` | нет |
| `RateLimit.SignalRWindowSeconds` | `int` | Core | — | Core: `Veriqa:RateLimit:SignalRWindowSeconds` | `60` | нет |
| `RateLimit.TokenPermitLimit` | `int` | Core | — | Core: `Veriqa:RateLimit:TokenPermitLimit` | `20` | нет |
| `RateLimit.TokenWindowSeconds` | `int` | Core | — | Core: `Veriqa:RateLimit:TokenWindowSeconds` | `60` | нет |
| `RateLimit.UserAuthPermitLimit` | `int` | Core | — | Core: `Veriqa:RateLimit:UserAuthPermitLimit` | `5` | нет |
| `RateLimit.UserAuthWindowSeconds` | `int` | Core | — | Core: `Veriqa:RateLimit:UserAuthWindowSeconds` | `60` | нет |
| `RateLimit.WebhookPermitLimit` | `int` | Core | — | Core: `Veriqa:RateLimit:WebhookPermitLimit` | `100` | нет |
| `RateLimit.WebhookWindowSeconds` | `int` | Core | — | Core: `Veriqa:RateLimit:WebhookWindowSeconds` | `60` | нет |
| `ScopesClaims.Catalog` | `IReadOnlyList`1\` | Tenant · Core | — | Tenant: без пути<br />Core: без пути | не объявлен | нет |
| `ScopesClaims.ClaimLevel` | `ClaimRequirementLevel` | Application | `claim` | Application: `ClaimLevels:{claim}` | не объявлен | нет |
| `ScopesClaims.RequireEmail` | `bool` | Application · Tenant · Core | — | Application: `RequireEmail`<br />Tenant: `RequireEmail`<br />Core: `Veriqa:ScopesClaims:RequireEmail` | `false` | нет |
| `ScopesClaims.RequirePhone` | `bool` | Application · Tenant · Core | — | Application: `RequirePhone`<br />Tenant: `RequirePhone`<br />Core: `Veriqa:ScopesClaims:RequirePhone` | `false` | нет |
| `Transaction.DefaultTimeZone` | `string` | UiConfig | — | UiConfig: `default_time_zone` | не объявлен | нет |
| `Transaction.TtlSeconds` | `int` | UiConfig | — | UiConfig: `ttl_seconds` | не объявлен | нет |
| `TransactionEngine.RejectBots` | `bool` | Application · Core | — | Application: `RejectBots`<br />Core: `Veriqa:TransactionEngine:RejectBots` | не объявлен | нет |
| `UiConfig.SelectorAssigned` | `bool` | UiConfig | — | UiConfig: `schema_version` | не объявлен | нет |

## Дальше

<CardGroup cols={2}>
  <Card title="Настройка каналов" icon="comments" href="/docs/ru/guides/channels">
    MAX, Telegram, WhatsApp, другие мессенджеры и Email — от начала до конца.
  </Card>

  <Card title="Продакшн-харденинг" icon="shield-check" href="/docs/ru/guides/hardening">
    Что проверить перед выкаткой.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.