> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Интеграция через API

> Как скрипт или другой сервис управляет Omniflow через /v1/admin: bearer-токен, ключи идемпотентности, курсоры, ошибки RFC 9457, идентификаторы запросов и ограничения частоты запросов

HTTP API Omniflow разделён на три префикса, которые никогда не делят между собой
ни маршрутизатор, ни учётные данные, ни стек middleware. Эта страница про
`/v1/admin` — поверхность, которую установка автоматизирует общим bearer-токеном
из скрипта, задания CI или другого сервиса.

| Префикс | Учётные данные | Дополнительные проверки | Для кого |
| - | - | - | - |
| `/v1/panel` | сессионная cookie `__Host-omniflow_admin` (`omniflow_admin` без TLS) | `X-CSRF-Token` на небезопасных методах, право доступа RBAC на каждый маршрут, состояние TOTP-проверки | Оператор-человек в браузерном рабочем пространстве `/admin` |
| `/v1/admin` | `Authorization: Bearer <APP_OPERATOR_TOKEN>` | нет — ни CSRF, ни RBAC; каждое изменение записывается в аудит как `operator-token` | Машина, действующая от имени установки |
| `/v1/account` | сессионная cookie `__Host-omniflow_account` (`omniflow_account` без TLS) | `X-CSRF-Token`, повторная аутентификация (step-up) на четырёх маршрутах | Клиент в браузерной панели `/account` |

Объединение любых двух из них означало бы, что один молча наследует middleware
другого, а учётные данные клиента никогда не должны дотягиваться до
операторского маршрута. Структурное разделение делает это свойством таблицы
маршрутов, а не проверкой, о которой кто-то должен помнить, и есть тест,
утверждающий, что клиентский маршрутизатор отвечает `404` на
`/v1/panel/auth/session`, `/v1/admin/orders` и `/v1/panel/admins`.

## Когда использовать `/v1/admin`, а не `/v1/panel`

Используйте `/v1/admin`, когда вызывающая сторона — не браузер. Здесь нет сессии,
которую нужно установить, нет CSRF-токена, который нужно держать в согласии с
ротацией, и нет хранилища cookie. Это единственный скриптуемый способ создать
заказ, принять платёж, зарегистрировать возврат, скорректировать кошелёк,
запустить импорт из Remnawave и поставить в очередь операцию выдачи доступа.

Используйте `/v1/panel`, когда вызывающая сторона — человек, или когда вам нужно
то, что `/v1/admin` не предоставляет: служба поддержки, маркетинговые кампании,
редактор каталога, настройки установки, журнал аудита, разбор рисков. Это также
единственная поверхность с авторизацией по отдельным возможностям: интеграция,
которая должна уметь читать, но не оформлять возвраты, живёт за учётной записью
оператора с узкой ролью, а не за общим токеном.

> [!WARNING]
>
> `APP_OPERATOR_TOKEN` даёт доступ **ко всем** эндпоинтам `/v1/admin`, включая
> `POST /v1/admin/payments/{paymentID}/refunds` и
> `POST /v1/admin/customers/{customerID}/wallet-adjustments`. На этом префиксе нет
> разграничения по возможностям и нет ролей. Передав токен третьей стороне, вы
> передаёте ей всю поверхность целиком. Считайте его учётными данными уровня
> установки и меняйте его правкой окружения с последующим перезапуском API.

Каждый изменяющий состояние запрос на этом префиксе — всё, кроме `GET`, `HEAD` и
`OPTIONS`, — после обработки записывается в журнал аудита с типом актора
`operator` и идентификатором `operator-token`, действием `admin_api.<метод>`
(например, `admin_api.post`), категорией `system`, исходом (`failure` для любого
`4xx` или `5xx`), а также шаблоном маршрута и статусом в метаданных. Токен не
называет человека, поэтому журнал не может сказать *кто*; но он говорит, что
изменение сделал общий токен, а не оператор в панели, и по этому актору можно
фильтровать в браузере аудита. Чтения и запросы, отклонённые из-за неверного
токена, не записываются.

Ничто внутри этого репозитория не вызывает `/v1/admin`: Telegram-бот работает с
PostgreSQL и Remnawave внутри процесса. Этот префикс существует исключительно как
поверхность для внешних интеграций и автоматизации.

## Аутентификация

Задайте `APP_OPERATOR_TOKEN` длинным случайным секретом и передавайте его в каждом
запросе:

```
Authorization: Bearer <APP_OPERATOR_TOKEN>
```

Сравнение сначала проверяет длину, а затем выполняется за постоянное время; пустой
токен в конфигурации отклоняет всё. Отсутствующий или неверный токен —
`401 unauthorized`.

API **требует** `APP_OPERATOR_TOKEN`, чтобы вообще собрать свой коммерческий
рантайм, наряду с 32-байтным `APP_DATA_ENCRYPTION_KEY`, `APP_REMNAWAVE_URL`,
`APP_REMNAWAVE_TOKEN` и `APP_VALKEY_URL`. Он не является необязательным даже для
установки, которая никогда не обращается к этому префиксу, — см.
[Конфигурацию](https://omniflow-vpn.mintlify.site/ru/getting-started/configuration.md).

Поскольку сессии нет, некоторые обработчики принимают действующего оператора как
**данные, которые сервер не может проверить**: `operatorId` в теле
`PUT /v1/admin/maintenance` (`400 missing_operator`, если он отсутствует) и
`operatorID` в решениях по ручным платежам и корректировках кошелька. Эти значения
попадают в запись. Передавайте что-то осмысленное и не считайте их
аутентификацией.

## Правила запросов

* `Content-Type: application/json` в любом запросе с телом.
* Тела декодируются с **отклонением** неизвестных полей и должны содержать ровно
  одно значение JSON. Лишнее поле «на будущее» или данные после значения дают
  `400 invalid_request`.
* Размер тела ограничен 1 МиБ.
* Обработчики `/v1/admin` декодируют данные в структуры Go без JSON-тегов, поэтому
  имена полей сопоставляются без учёта регистра: `customerID`, `customerId` и
  `CustomerID` работают одинаково. Схемы OpenAPI называют их в camelCase;
  принимаются оба варианта.
* Ко всем маршрутам применяется серверный таймаут обработчика в 60 секунд.

## Ключи идемпотентности

Каждое изменение, порождающее движение денег, выдачу доступа или проводку в книге,
принимает заголовок запроса `Idempotency-Key`. Это операции, в которых дважды
отправленная форма, повтор запроса прокси или переотправленный вебхук не должны
породить два заказа, два возврата, два зачисления в кошелёк или двух пользователей
Remnawave.

Генерируйте одну уникальную строку на одно логическое действие и повторно
используйте её дословно при каждой повторной попытке этого действия. Ключ
пробрасывается в доменный слой и именно он делает нижележащий SQL идемпотентным;
общего кэша повторов на уровне middleware нет, поэтому ключ, переиспользованный для
двух *разных* действий, — ошибка вызывающей стороны.

Валидация — это исключительно проверка длины: от 8 до 128 символов, без правил на
набор символов. Отсутствующий или выходящий за диапазон ключ на `/v1/admin` —
`400 missing_idempotency_key`.

| Эндпоинт | Ключ обязателен |
| - | - |
| `POST /v1/admin/orders` | да |
| `POST /v1/admin/orders/{orderID}/cancel` | да |
| `POST /v1/admin/orders/{orderID}/payments` | да |
| `POST /v1/admin/payments/{paymentID}/refunds` | да |
| `POST /v1/admin/payments/{paymentID}/manual-decision` | да |
| `POST /v1/admin/customers/{customerID}/wallet-adjustments` | да |
| `POST /v1/admin/entitlements/{entitlementID}/operations` | да |
| `POST /v1/admin/payments/{paymentID}/reconcile` | **нет** |

> [!NOTE]
>
> Контракт помечает `Idempotency-Key` как обязательный для
> `POST /v1/admin/payments/{paymentID}/reconcile`, но обработчик его никогда не
> читает. Сверка идемпотентна сама по себе — она заново опрашивает провайдера
> через платёжный сервис. Отправка заголовка безвредна.

Остальные префиксы ведут себя иначе, и это важно, если вы обращаетесь и к ним:
`/v1/account` отвечает `400 idempotency_key_required` (кроме
`POST /v1/account/shop/purchase`, который отвечает `422`), а `/v1/panel` отвечает
`422 idempotency_key_required` и принимает любую непустую строку без проверки
длины. Вебхуки провайдеров дедуплицируются по собственному идентификатору события
провайдера, а не по этому заголовку.

## Курсорная постраничная выборка

Списки, которые могут расти неограниченно, возвращают
`{"items":[…], "nextCursor":"…"}` и принимают `?cursor=<value>&pageSize=<n>`.
Пустой `nextCursor` означает последнюю страницу. Это постраничная выборка по ключу
(keyset) по `(timestamp, id)`, а не через `OFFSET`: кодирование ключа сортировки
удерживает границу страницы стабильной, пока вставляются новые строки, чего смещение
обещать не может — иначе интеграция, обходящая журнал аудита во время поступления
новых событий, видела бы строки дважды или пропускала их.

Запросите первую страницу без `cursor`, передавайте возвращённый `nextCursor`
обратно дословно и остановитесь, когда он пуст. **Считайте значение
непрозрачным.** В кодовой базе оно кодируется тремя разными способами — base64url,
открытый текст и пара параметров `cursor` + `cursorId` — и возврат значения без
изменений корректен во всех случаях, а его разбор — нет.

Некоторые списки `/v1/admin` вообще не используют курсоры и принимают обычный
`limit`:

| Эндпоинт | По умолчанию | Максимум |
| - | - | - |
| `GET /v1/admin/jobs` | 50 | 200 |
| `GET /v1/admin/webhooks` | 50 | 200 |
| `GET /v1/admin/backups` | 20 | 100 |
| `GET /v1/admin/fulfillment/drifts` | 100 | 500 |
| `POST /v1/admin/imports/remnawave/preview` (`pageSize`) | 100 | 500 |
| `POST /v1/admin/imports/remnawave/{importID}/apply` (`limit`) | 100 | 500 |

## Ответы об ошибках

Каждый отказ на всех трёх префиксах формируется одной функцией и возвращается в
формате RFC 9457:

```json theme={null}
{
  "type": "https://omniflow.dev/problems/order_rejected",
  "title": "Unprocessable Entity",
  "status": 422,
  "detail": "plan version is retired",
  "request_id": "omniflow/7f3c9a-000012"
}
```

`Content-Type` — `application/problem+json`. **Ветвитесь по последнему сегменту
пути в `type`** — это стабильный машиночитаемый код. По одному только статусу
нельзя отличить «промокод истёк» от «промокод опускает цену ниже себестоимости», а
`detail` — это проза, которая меняется, когда кто-то улучшает формулировку.
`title` — всегда текст HTTP-статуса, никогда не собственный заголовок.

`request_id` записан в snake\_case, тогда как все остальные поля API — в camelCase.
Так пишет код и так объявляет схема.

Коды, которые стоит обрабатывать явно:

| Код | Статус | Значение |
| - | - | - |
| `unauthorized` | 401 | Bearer-токен отсутствует или неверен |
| `invalid_request` | 400 | Некорректный JSON, неизвестное поле или более одного значения JSON |
| `missing_idempotency_key` | 400 | Изменяющий запрос без корректного ключа длиной 8–128 символов |
| `missing_operator` | 400 | В запросе на изменение режима обслуживания нет `operatorId` |
| `invalid_state` | 400 | Неизвестный фильтр `state` в `GET /v1/admin/jobs` |
| `order_rejected` / `payment_rejected` | 422 | Доменный слой отклонил запрос; причина указана в `detail` |
| `order_cancellation_rejected` | 409 | Заказ находится не в том состоянии, из которого его можно отменить |
| `not_provisioned` | 409 | У подписки ещё нет пользователя Remnawave |
| `promo_rate_limited` | 429 | Слишком много попыток ввода промокода для этого клиента |
| `rate_limit_unavailable` | 503 | Valkey недоступен, и ограничение срабатывает на отказ |
| `jobs_unavailable` | 500 или 503 | В процессе нет клиента очереди задач либо хранилище задач не ответило |
| `maintenance_active` | 503 | Режим обслуживания блокирует новые покупки |

Одно и то же нижележащее условие может приходить с разными кодами на разных
префиксах: недоступность Remnawave при получении списка устройств — это
`502 remnawave_unavailable` на `/v1/panel` и `503 upstream_unavailable` на
`/v1/account`. Инструкция, которая трактует «Remnawave лежит» как одно условие,
должна знать оба кода.

Два вида отказов полностью выпадают из формата problem: коллектор телеметрии
отвечает обычным текстом, а `GET /v1/panel/audit/export` отдаёт CSV потоком уже
после того, как записан `200`, поэтому сбой в середине потока обрезает файл, а не
порождает тело с описанием проблемы.

## Идентификаторы запросов

Каждому запросу присваивается идентификатор, и каждый ответ несёт его в
`X-Request-ID`. То же значение появляется как `request_id` в телах ошибок, как
`requestId` в строках аудита, в идентификаторах корреляции выдачи доступа и в
событиях безопасности клиента.

Считывайте его из каждого ответа и записывайте в лог. Это единственная строка,
которая связывает «клиент говорит, что платёж не прошёл» со строкой лога
обработчика, строкой аудита, операцией выдачи доступа и вызовом Remnawave, — и при
этом никому не нужно передавать идентификатор клиента по каналу поддержки.

На `/v1/panel` идентификатор запроса ещё и служит единицей дедупликации для дважды
отправленной операторской формы: операция над подпиской выводит свой ключ
идемпотентности как `panel:<subscriptionID>:<operation>:<requestID>`.

## Ограничения частоты запросов

Ограничения реализованы как счётчики с фиксированным окном в Valkey. К `/v1/admin`
относится только одно из них:

| Область | Субъект | Лимит | Окно | Маршрут |
| - | - | - | - | - |
| `promo` | ID клиента | 10 | 1 минута | `POST /v1/admin/orders`, когда задан `promoCode` |
| `admin-login-account` | нормализованный email | 10 | 1 минута | `POST /v1/panel/auth/login` |
| `admin-login-ip` | IP клиента | 30 | 1 минута | `POST /v1/panel/auth/login` |
| `admin-challenge` | IP клиента | 15 | 1 минута | `POST /v1/panel/auth/challenge` |
| `admin-reset` | IP клиента | 5 | 1 час | `POST /v1/panel/auth/password-reset` |
| `account_contact` | ID клиента | 10 | 1 час | `POST /v1/account/contacts` |
| `account_export` | ID клиента | 3 | 1 час | `POST /v1/account/privacy/export` |

Больше ничто на `/v1/admin` не ограничивается по частоте. **Заголовка
`Retry-After` нет**, как нет и заголовков с состоянием лимита в ответах, поэтому
клиент, упёршийся в ограничение, должен выдерживать паузы по собственному
расписанию.

Ограничения **срабатывают на отказ**. Если Valkey недоступен, ограничитель
возвращает ошибку и запрос отклоняется, потому что сбой не должен молча снимать
защиту. На пути промокода это `503 rate_limit_unavailable`; на путях входа
оператора это означает, что вход полностью заблокирован, пока Valkey лежит.

Адрес клиента, по которому считается ограничение, берётся из разрешения доверенных
прокси, описанного в [Безопасности](https://omniflow-vpn.mintlify.site/ru/operations/security.md). При пустом
`APP_TRUSTED_PROXIES` заголовок `X-Forwarded-For` игнорируется и используется адрес
сокета — а значит, развёртывание за прокси, где эта переменная забыта, ограничивает
всех вызывающих как одного субъекта.

## Заголовок `X-Operator-Reason`

Этот заголовок принадлежит `/v1/panel`, а не `/v1/admin`. Он читается в каждом
запросе `/v1/panel` и несёт *причину*, по которой оператор вносит изменение:
причина едет в заголовке, а не в каждом теле, потому что она нужна почти каждому
изменяющему запросу здесь, и протаскивание её через шестьдесят структур запросов
гарантированно привело бы к тому, что в одной из них о ней забудут. Значение
попадает в журнал аудита, а для некоторых записей — и в саму запись.

Отправляйте `X-Operator-Reason: <1..400 символов>` в тех изменяющих запросах
`/v1/panel`, которые её требуют: предпросмотр массовой операции, отзыв подарка,
изменение статуса и жизненного цикла клиента, регистрация возврата, завершение или
отмена выдачи товара, ручное переключение режима обслуживания, решения по
блок-листу и апелляции, разбор аномалий и добавление клиента в белый список рисков.
Отсутствующая причина возвращается как `422 invalid_request` с деталью
*«Those details are not valid, or a reason is required.»*

Обработчики `/v1/admin` этот заголовок игнорируют. Там, где им нужно обоснование,
они принимают его в теле — `reason` в
`POST /v1/admin/customers/{customerID}/lifecycle`,
в `POST /v1/admin/orders/{orderID}/cancel` и в `PUT /v1/admin/maintenance`.

## Разобранный пример

Создадим заказ для существующего клиента и запустим по нему платёж.

```bash theme={null}
API=https://api.example.com
TOKEN=$APP_OPERATOR_TOKEN

# 1. Create the order. The key is yours to choose and must survive a retry.
curl -sS -D - -X POST "$API/v1/admin/orders" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 2026-08-13-order-7f3c9a" \
  -d '{
        "customerID": "6f1d0b6e-4b64-4c1e-9e3f-2b1f0f1a8d21",
        "planVersionID": "0d9b1f2c-6a44-4f0e-9d7a-11c2f3e4a5b6",
        "currency": "RUB",
        "operation": "new",
        "promoCode": ""
      }'
```

```http theme={null}
HTTP/1.1 201 Created
Content-Type: application/json
X-Request-ID: omniflow/7f3c9a-000012

{"id":"c1a9…","state":"pending","subtotalMinor":49000,"currency":"RUB","expiresAt":"2026-08-13T12:40:00Z"}
```

```bash theme={null}
# 2. Create the payment intent. A different action, so a different key.
curl -sS -X POST "$API/v1/admin/orders/c1a9…/payments" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 2026-08-13-pay-7f3c9a" \
  -d '{
        "provider": "yookassa",
        "description": "Omniflow subscription",
        "returnURL": "https://shop.example.com/thanks"
      }'
```

```json theme={null}
{"id":"9b2e…","provider":"yookassa","status":"pending","checkoutUrl":"https://yoomoney.ru/checkout/…"}
```

Отправьте клиента на `checkoutUrl`. Вебхук провайдера завершает платёж на
`POST /v1/payments/webhooks/{provider}`; больше от интеграции ничего не требуется.
Опрашивайте `GET /v1/admin/orders/{orderID}`, если вам нужно наблюдать за
результатом, а конечный автомат описан в
[Заказах и платежах](https://omniflow-vpn.mintlify.site/ru/commerce/orders-payments.md).

Повтор любого из этих вызовов с тем же `Idempotency-Key` возвращает запись,
созданную первой попыткой, а не создаёт вторую. Повтор с новым ключом создаёт
второй заказ.

## Что покрывает `/v1/admin`

Тридцать эндпоинтов, сгруппированных по назначению.

| Область | Эндпоинты |
| - | - |
| Каталог | `POST /plans`, `POST /plans/{planID}/versions`, `POST /promotions` |
| Клиенты | `PATCH /customers/{customerID}`, `POST /customers/{customerID}/identities`, `DELETE /customers/{customerID}/identities/{identityID}`, `POST /customers/{customerID}/contacts`, `POST /customers/{customerID}/consents`, `POST /customers/{customerID}/lifecycle` |
| Импорт из Remnawave | `POST /imports/remnawave/preview`, `…/{importID}/resume`, `…/{importID}/apply` |
| Заказы и платежи | `POST /orders`, `GET /orders/{orderID}`, `POST /orders/{orderID}/cancel`, `POST /orders/{orderID}/payments`, `POST /payments/{paymentID}/reconcile`, `POST /payments/{paymentID}/refunds`, `POST /payments/{paymentID}/manual-decision` |
| Кошелёк | `POST /customers/{customerID}/wallet-adjustments` |
| Выдача доступа | `GET /fulfillment/drifts`, `POST /entitlements/{entitlementID}/operations` |
| Эксплуатация | `GET /jobs`, `POST /jobs/{jobID}/retry`, `POST /jobs/{jobID}/cancel`, `GET /webhooks`, `GET /outbox`, `GET /backups`, `GET /maintenance`, `PUT /maintenance` |

Что они намеренно не возвращают:

* `GET /v1/admin/webhooks` никогда не отдаёт исходное тело от провайдера. Это
  собственная полезная нагрузка провайдера, и в ней могут быть платёжные данные; для
  ответа на вопрос «пришло ли это и было ли принято» достаточно статуса и результата
  проверки подписи.
* `GET /v1/admin/jobs` возвращает сводку аргументов задачи, а не сами аргументы, и
  прогоняет последнюю ошибку через маскирование чувствительных данных, поэтому ссылка
  на подписку или bearer-токен из сообщения вышестоящей системы никогда не покидает
  процесс.
* Эндпоинт жизненного цикла применяет изменение на уровне изоляции `Serializable` и
  соблюдает конечный автомат клиента: `suspend` — только из `active`, `restore` —
  только из `suspended`, `anonymize` — только из `deleted` и только после того, как
  истёк срок хранения.

Три оговорки про группу эксплуатации:

* `GET /v1/admin/jobs` и маршруты retry/cancel отвечают `503 jobs_unavailable` в
  процессе, к которому не подключён клиент очереди задач. Фильтр `state` по
  умолчанию — `dead_letter`; остальные принимаемые значения — `retryable`,
  `running`, `available`, `scheduled` и `completed`.
* Долговременное хранилище задач — River, чья схема поставляется вместе с
  миграциями Atlas, поэтому на стандартной установке эти эндпоинты читают
  собственные строки очереди River. Представление `/v1/panel/system/jobs`,
  показывающее *операции выдачи доступа*, — другое: оно опирается на
  собственные таблицы Omniflow.
* `GET /v1/admin/outbox` сообщает количество неопубликованных доменных событий и
  возраст самого старого. Ничто в текущей сборке никогда не помечает строку outbox
  опубликованной, поэтому это число растёт и не является полезным сигналом о
  накопившемся отставании. См. [Наблюдаемость](https://omniflow-vpn.mintlify.site/ru/operations/observability.md).

## Эндпоинты, не требующие учётных данных

Пять видов маршрутов отвечают без каких-либо учётных данных, и каждый защищён
чем-то помимо аутентификации:

* `/healthz`, `/livez`, `/readyz` — пробы. `/readyz` публикует стабильную
  *классификацию* (`timeout`, `unavailable`), а не сообщение драйвера, поэтому не
  может раскрыть строку подключения.
* `/metrics` — отдаётся только когда `APP_METRICS_ENABLED` включён, и предполагается,
  что доступ к нему ограничен на обратном прокси. Все метки ограничены по мощности:
  используется шаблон маршрута, а не конкретный путь с идентификаторами.
* `GET /v1/catalog/plans?locale=ru|en` — анонимное чтение витрины. Любая локаль,
  кроме `en`, откатывается к `ru`.
* `POST /v1/payments/webhooks/{provider}` — не аутентифицирован по необходимости и
  защищён проверкой подписи провайдера. Неудачная проверка всё равно записывается и
  отвечает `400 webhook_rejected`; тело больше 1 МиБ — `413 payload_too_large`;
  повторное событие провайдера разрешается в `{"status":"duplicate"}`.
* `POST /v1/telemetry/events` — присутствует, только когда
  `APP_TELEMETRY_COLLECTOR_ENABLED` включён (по умолчанию выключен). Это приёмная
  сторона, которую разворачивает установка-коллектор, а не то, что открывает наружу
  обычное развёртывание.

## Контракт OpenAPI

`api/openapi.yaml` — исходный контракт (OpenAPI 3.0.3), именно он рендерится в
публикуемом справочнике API. Из него генерируются и серверные типы Go, и
TypeScript-биндинги, а CI падает, если изменение контракта закоммичено без их
перегенерации.

Учитывайте эти пробелы, прежде чем генерировать по нему клиент:

* **В нём описаны 113 из 195 маршрутов `/v1/panel`.** Целые области отсутствуют:
  настройки установки, реестры AI и MCP, служба поддержки, маркетинг, лояльность,
  OIDC для операторов, представления состояния здоровья и режима обслуживания.
  Поверхности `/v1/admin` и `/v1/account` описаны полностью.
* **Ни одна операция не ссылается на схемы безопасности `panelSession` или
  `accountSession`.** Схемы определены, а корневого `security` нет, поэтому в
  терминах OpenAPI каждая операция `/v1/panel` и `/v1/account` читается как
  неаутентифицированная. Только 30 операций `/v1/admin` несут
  `security: [{operatorToken: []}]`. Сгенерированный клиент не будет знать, что
  вызовам панели нужны учётные данные.
* **Требуемые права доступа указаны только в текстовых описаниях**, поэтому никакой
  инструментарий не может их извлечь. Авторитетный ответ — это
  `requirePermission(...)` в точке монтирования каждого маршрута.
* **Ответы не валидируются по контракту во время выполнения.** Обработчики
  декодируют данные в анонимные структуры и пишут вручную собранные map;
  сгенерированные серверные типы Go не импортируются нигде. Ответ может разойтись со
  своей схемой без падения тестов.
* `info.version` содержит `1.0.0`. Он отражает линию выпусков, а не отдельную
  версию контракта, поэтому по нему нельзя понять, какие из перечисленных выше
  пробелов уже закрыты.
* Две операции задокументированы безусловно, но в коде условны: `GET /metrics` и
  `POST /v1/telemetry/events`. В более общем случае маршрут `/v1/panel` или
  `/v1/account`, чей сервис не подключён, **отсутствует (404)**, а не отдаёт `503` —
  `503` сообщил бы анонимному вызывающему, что именно настроил оператор.

## Связанные страницы

> **[Конфигурация](https://omniflow-vpn.mintlify.site/ru/getting-started/configuration.md)**
>
> `APP_OPERATOR_TOKEN`, `APP_TRUSTED_PROXIES` и остальное окружение.

> **[Безопасность](https://omniflow-vpn.mintlify.site/ru/operations/security.md)**
>
> Поверхности с учётными данными, что никогда не пишется в логи и как запечатываются секреты.

> **[Заказы и платежи](https://omniflow-vpn.mintlify.site/ru/commerce/orders-payments.md)**
>
> Конечный автомат заказа, который приводит в движение разобранный пример.

> **[Задачи и очереди](https://omniflow-vpn.mintlify.site/ru/operations/jobs-and-queues.md)**
>
> Что показывают эндпоинты задач и что на самом деле повторяется.


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