# HTTP API и ошибки ## Источник контракта Backend генерирует OpenAPI из собственного публичного API. Этот сгенерированный OpenAPI — исполняемый источник HTTP-контракта. Frontend использует предоставленный OpenAPI snapshot как актуальный вход и локально генерирует типизированный клиент. Backend владеет контрактом, а подготовка и доставка схемы находятся вне frontend. Это разделение принято в [ADR 002](../adr/002-nest-generated-openapi-contract.md) и [ADR 014](../adr/014-trusted-openapi-input.md). Backend командой `npm run openapi:generate` создаёт `openapi.json` без запуска сервера и не хранит результат в Git. Contract test сравнивает вывод генератора с Nest runtime schema настроенного приложения. Frontend хранит полученный artifact у себя и проверяет соответствие generated types этой копии. CI не скачивает соседние репозитории и не сравнивает их текущие revisions. Обновление входного artifact потребителя выполняется отдельно с review; совместимость развёрнутых версий не доказывается одним зелёным локальным CI. API имеет prefix `/api/v1`; Swagger публикуется для локальной разработки по environment policy, а не используется frontend в runtime. ## Инвестиционные данные и синхронизация Затронутые GET инвестиционных ресурсов возвращают сгенерированный contract `{ data, meta }`. `data` nullable: `null` означает отсутствие complete result, а подтверждённый пустой массив является available complete result. `meta` независимо описывает freshness, completeness, `lastSuccessfulAt`, resource-relevant `lastAttempt` и availability. `lastAttempt` имеет состояния `none`, `running`, `succeeded`, `partial` и `failed`; его неуспех не заменяет сохранённый complete result или его время. `Portfolio.isStale` остаётся специфичным для snapshot полем, а не заменой состояния попытки. Только Accounts дополнительно возвращает top-level `sync`: `null`, если у текущего подключения ещё не было часовой попытки, иначе connection-wide наблюдаемый результат. Это наблюдение, не команда. Обычный GET и retry после проверки доступа читают локальное состояние и не начинают личную работу. Устаревший `sync=observe`, пока он сохранён для совместимости маршрута, также только читает. Request-started screen-sync command/status не является текущей AS-IS моделью. Безопасные ошибки завершённой часовой попытки передаются в `lastAttempt` и Accounts `sync`; HTTP transport/access/input errors сохраняют Problem Details. Старые данные при временной source failure сохраняют исходное успешное время. Ни одно поле не раскрывает connection revision. Детали поведения — [свежесть и полнота](cross-cutting/data-freshness.md). Envelope несовместим с прежними bare-array ответами: backend/frontend поставляются согласованной парой. Финальный artifact передаётся явно; локальная генерация и CI frontend читают только собственный snapshot. ## Validation и identity Backend преобразует и проверяет входные данные на общей HTTP-границе. Неизвестные свойства запроса удаляются; сам факт их передачи не обещает отказа. Проверка формата не заменяет бизнес-ограничения и проверку доступа к счёту. Аутентификация подтверждает личность пользователя; accountId из URL сам по себе не доказывает доступ к брокерским данным. ## Error contract Общая граница обработки HTTP-ошибок возвращает `application/problem+json` со `status` и публичным `code`. Неожиданный exception возвращает `500` / `INTERNAL_SERVER_ERROR`, без исходного message/stack. Типичный ответ: ```json { "status": 401, "code": "UNAUTHORIZED" } ``` Для input validation контракт требует `400`, `VALIDATION_ERROR` и `errors` как массив `{ pointer, detail }`, где pointer адресует поле запроса. Это требование проверяется реальным HTTP-ответом после всех стадий обработки запроса. Глобальная валидация передаёт фильтру структурированные ошибки без исходных значений и DTO, а фильтр формирует JSON Pointer, включая вложенные поля. HTTP suites подтверждают детали ошибок регистрации, банковской карты и синхронизации; OpenAPI отдельно фиксирует форму ответа. Ответ не включает автоматически все поля RFC (`type`, `title`, `instance`). Добавление обязательного поля является изменением публичного контракта. Клиент выбирает поведение по публичному коду, а не по внутреннему тексту ошибки. ## Domain errors и auth Специальный code определяется сценарием; общее преобразование ошибок и безопасная форма ответа принадлежат HTTP-границе. Auth endpoints используют свои status и cookie operations: [Authentication](../domains/authentication/overview.md). T-Bank token failure и временная недоступность имеют разные значения: [Connection API](../integrations/tbank.md#состояние-и-ошибки). Raw upstream/Prisma errors и secrets не передаются frontend. ## Наблюдаемость Backend пишет структурированные JSON-события в stdout через Pino. Обработка HTTP-запроса получает серверный request ID, который возвращается заголовком `X-Request-ID`; допустимый входящий идентификатор сохраняется отдельно как client request ID. Это позволяет сопоставить публичную ошибку с обработкой запроса без передачи внутреннего exception в ответ API. Журнал завершения запроса содержит шаблон маршрута, метод, статус, длительность и при наличии публичный error code. События внешней границы добавляют операцию, результат и число попыток. Разрешённые поля ограничены; полные запросы и ответы, token, cookie и Authorization не являются данными журнала. Фильтрация полей и redaction не защищают секрет, вручную включённый в свободный текст сообщения или stack. Выбор logger и граница ответственности описаны в [ADR 008](../adr/008-pino-backend-logging.md); согласованное поведение — в workspace-спецификации [Structured logging](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/backend-structured-logging/spec.md). Сборщик, срок хранения журналов и alerting не определяются приложением.