---
title: "Предложить классификацию финансовой операции"
method: POST
path: "/api/v1/app/finance/transactions/{id}/classification-suggestions"
tags: ["App runtime"]
---

# Предложить классификацию финансовой операции

`POST /api/v1/app/finance/transactions/{id}/classification-suggestions`

## Request

- Base URL: `https://erp.akeda.ru`
- URL: `https://erp.akeda.ru/api/v1/app/finance/transactions/{id}/classification-suggestions`
- Auth: HTTP bearer

Ответ расширения на точку `finance.classification_provider.v1`. Запрос приезжает ему событием `finance.transaction.uncategorized.v1` — минимальным снимком операции: сумма, дата, направление, назначение и контрагент ссылкой. Ни остатка по счёту, ни соседних операций, ни самого счёта в снимке нет: чтобы предложить статью, знать, сколько у кабинета денег, незачем.

РЕКОМЕНДАЦИЯ ЛОЖИТСЯ РЯДОМ С ОПЕРАЦИЕЙ И НЕ ПРИМЕНЯЕТСЯ. Этот вызов не меняет ни классификацию операции, ни движения регистров, ни документ: он пишет ровно одну строку — мнение установки. Статью проставляет человек штатной командой `financeCategorizeTransaction`, и до его нажатия операции нет ни в ДДС, ни в ОПиУ, ни в балансе. Так устроен основной поток ADR: асинхронный внешний результат сначала ложится проверкой, рекомендацией или черновиком, а окончательное действие выполняет команда Akeda.

ОБЛАСТЬ `finance:suggest`, а не `finance:write`. Это разные обещания на экране согласия: «предлагает, как разнести ваши операции» и «разносит ваши операции». Совпадение области проверяется точным равенством, поэтому `finance:write` эту дверь не открывает, а `finance:suggest` не открывает ни одной другой — ни чтения операций, ни классификации.

КАБИНЕТА В АДРЕСЕ НЕТ ни одним сегментом: он берётся из токена установки. Операция чужого кабинета отвечает 404 — тем же, чем несуществующая. Уже классифицированная операция отвечает 409: совет ей больше не нужен, а показанный рядом с решением человека он выглядел бы спором с ним.

ПОВТОР — ОБНОВЛЕНИЕ, А НЕ ВТОРАЯ СТРОКА. Доставка события повторяется, и приёмник обязан быть идемпотентным: ключ — естественная пара «операция + установка», у одной установки на одну операцию бывает ровно одно действующее мнение. Заголовок `Idempotency-Key` здесь не читается: он слабее — доставка webhook повторяется без него, — а точный повтор ответа ничего не даёт, потому что ответ не несёт ни секрета, ни новых сведений. Тот же ответ на уже отклонённую рекомендацию решения человека не отменяет; другая статья возвращает её в ожидание — это новое мнение, а не повтор старого.

Ссылки передаются парой «ключ справочника + идентификатор»: голый UUID может принадлежать другому справочнику или другому кабинету, и сервер проверяет и то, и другое.

## Path parameters

- `id` string, uuid, required

## Headers

- `X-Tenant` string, required

## Request body

- AppFinanceClassificationSuggestionInput: Ответ расширения на точку finance.classification_provider.v1. Автора в теле нет: установка, приложение и версия берутся из токена — иначе первое же расширение подписало бы рекомендацию соседним.
  - `cashflow_item` AppFinanceDirectoryRef, required: Ссылка на запись справочника: ключ и идентификатор. Голый UUID здесь не принимается — он доказывает, что строка есть, и ничего не говорит о том, из какого она справочника и чья. Ключ не тот — отказ directory_mismatch, записи нет в этом кабинете — directory_entry_unknown.
    - `directory_key` string, required: Полное имя справочника: core.items или core.contacts
    - `id` string, uuid, required
  - `contact` AppFinanceDirectoryRef: Ссылка на запись справочника: ключ и идентификатор. Голый UUID здесь не принимается — он доказывает, что строка есть, и ничего не говорит о том, из какого она справочника и чья. Ключ не тот — отказ directory_mismatch, записи нет в этом кабинете — directory_entry_unknown.
    - `directory_key` string, required: Полное имя справочника: core.items или core.contacts
    - `id` string, uuid, required
  - `confidence` number, required: Доля единицы, не проценты. Значение вне диапазона отклоняется кодом confidence_out_of_range: приславший 87 имел в виду проценты, и принять это молча значит показать человеку уверенность 8700 %.
  - `explanation` object, required: Обе половины обязательны — кабинет с английским интерфейсом не должен читать объяснение по-русски
    - `ru` string, required
    - `en` string, required

## Response `200`

Рекомендация записана рядом с операцией и ждёт решения человека

- AppFinanceClassificationSuggestionAccepted: Ответ расширению. Ровно то, что оно прислало само, плюс идентификатор строки и её состояние: ни назначения платежа, ни суммы, ни имени статьи здесь нет — иначе право писать рекомендации стало бы правом читать операции.
  - `suggestion_id` string, uuid, required
  - `transaction` string, uuid, required
  - `status` 'pending' | 'accepted' | 'rejected', required: pending, пока человек не решил. Повторный ответ той же установки на ту же операцию обновляет строку, а не заводит вторую
  - `updated_at` string, date-time, required

## Other responses

- `400`: Код отказа точки: directory_mismatch, directory_entry_unknown, confidence_out_of_range, explanation_incomplete
- `401`: Сессия, ключ или кабинет не подтверждены
- `403`: Текущая роль не имеет требуемого права
- `404`: transaction_not_found — в том числе если операция принадлежит другому кабинету
- `409`: transaction_already_classified: статью уже выбрал человек
- `503`: На этой сборке рекомендации расширений не подключены

---

[API](https://skmtc.dev/akeda/apis/akeda-erp-client-api.md) · [All operations](https://skmtc.dev/akeda/apis/akeda-erp-client-api/llms.txt) · [OpenAPI document](https://skmtc.dev/akeda/apis/akeda-erp-client-api/revisions/3c60ded179f2?raw)
