Справочник API «Потока»

Полный список маршрутов: 107 в 21 модуле. Всё живёт под /api.

Это справочник «что есть». Как пройти путь от ключа до оплаченного счёта — в руководстве по интеграции: там сценарий с рабочими curl.

Файл собран из кода и должен ему соответствовать: список маршрутов сверяет deploy/check-routes.py, и CI падает, если модуль появился, а его раздела здесь нет.

Два способа авторизоваться

Кто Заголовок Где работает
JWT человек в кабинете Authorization: Bearer <jwt> все модули
Ключ приложения чужая система Authorization: Bearer ooo_sk_… счета, контрагенты, вебхуки

Ключ приложения ограничен скоупами (invoices:read, invoices:write, counterparties:read, counterparties:write, webhooks:write) — выдавайте минимальный набор. Права человека — по политике доступа (docs/research/access-policy.md): у маршрута указан раздел и уровень (просмотр, изменение, управление) и право, если оно нужно («Закрывать период», «Выставлять счета и документы»…). Какие разделы и права у человека, отдаёт GET /api/auth/me (access). Человеку на маршрутах с ключами скоупы выдаются из его прав: просмотр раздела — :read, изменение — :write.

Компания запроса

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

X-Tenant-Id: <id компании>

Ключу приложения заголовок не нужен и не действует: ключ открывает ровно ту компанию, в которой его выпустили.

Статус оператора платформы (isPlatformAdmin) прав внутри компаний не даёт.

Маршруты

Архив вместо удаления. Контрагенты, договоры, категории, виды платежей, серии счетов, банковские счета и сотрудники убираются в архив: POST /api/<раздел>/:id/archive с { "archived": true }, возврат — с false. Ответ — сама запись с isArchived. Архивная запись остаётся в списках с isArchived: true (поле выбора должно уметь показать уже выбранную), в новых выборах её не предлагают. Удаление записи, на которую ссылаются счета, платежи, договоры или документы, отвечает 409 с archivable: true — такую убирают в архив. Последствия: архивный банковский счёт не загружает выписку (409 на /sync) и не входит в тариф; архивная категория не разносит новые платежи; архивный договор не подбирается к платежам, если его номер не назван в назначении. Системный вид платежа и основную серию убрать в архив нельзя. Архив сотрудника — это «уволен» (active: false): зарплата за прошлые месяцы считается по датам «в штате с/по», поэтому сотрудника с начислениями или документами не удаляют. Списки контрагентов и договоров фильтруются параметром archived (1 — только архивные, 0 — без них).

Бухгалтерия — /api/accounting

Журнал проводок, оборотно-сальдовая ведомость, учётная политика и сверка остатков на расчётных счетах. Проводки производные: их не вводят, а строят из событий «Потока» по версионированным правилам (rule, ruleVersion): операции выписки по виду платежа (51, 57, 58, 60, 62, 66, 68.90, 69, 70, 75, 91); выставленные документы продажи (62.01 / 90.01, НДС 90.03 / 68.02, сторно при аннулировании в более позднем месяце) и зачёт авансов 62.02 → 62.01 — по привязке оплаты к счёту, иначе FIFO по контрагенту; закрытые месяцы зарплаты (26 или 44 / 70, 70 / 68.01, 26 или 44 / 69.09), налог УСН и налог на прибыль из закрытых кварталов (99 / 68.12, 99 / 68.04) и погашение обязательств с ЕНС (68.xx, 69.09 / 68.90). Незакрытый месяц зарплаты в журнал не попадает — выплата без начисления даёт предупреждение. Каждая проводка ссылается на источник: sourceType + sourceId + lineNo. Займы (/loans): карточка с суммой, сроком и ставкой; на дату выдачи срок больше 12 месяцев — тело на 67, иначе на 66, выданный под проценты — на 58.03, выданный беспроцентный — на 76.09 (своему работнику — 73.01; это дебиторская задолженность, а не финансовое вложение — п. 2 ПБУ 19/02: строка 1230, часть со сроком погашения больше 12 месяцев после отчётной даты — 1190, п. 13–16 ФСБУ 4/2023). Работник — только сотрудник, выбранный в карточке: POST /loans принимает employeeId (только у выданного займа и не у организации с ИНН из 10 цифр — иначе 400), POST /loans/employee { loanId, employeeId | null } ставит или снимает отметку у заведённого займа (404 — займа нет, 409 — заём получен или заёмщик организация, 400 — сотрудника нет). В ответе у займа employeeId, employeeName и employeeHint: { id, name } | null — подсказка «ФИО заёмщика совпало с сотрудником, работавшим на дату договора», счёт она не меняет; рядом с items и candidates приходит employees: [{ id, name, active }] — список для выбора. ПДн: без права «Зарплаты и ПДн сотрудников» (hr.pii; у ключей приложений его нет, и раздел им закрыт) employees пуст и employeesHidden: true, employeeHint — null, у займа сотруднику (отмечен в карточке или заёмщик совпал по имени с сотрудником — то же правило, что у платежей в выписке) counterpartyName и employeeName — «Сотрудник», counterpartyInn пуст, counterpartyId — null, у платежей сотрудникам в candidates получатель — «Выплата сотруднику» / «Поступление от сотрудника»; employeeId, счёт и суммы остаются. POST /loans/employee и POST /loans с employeeId без этого права — 403. Беспроцентный заём, выданный до начала учёта и внесённый вводом остатков на 58.03 с тем же контрагентом, журнал переносит на 76.09 / 73.01 проводкой loan.reclass в день начала учёта (остаток 58.03 без контрагента — предупреждение журнала); в год переноса в checks отчётности есть запись loanReclass — сравнительная графа не пересчитана. У карточки со ставкой 0 без отметки interestFree в ответе — true. principalAccount в карточке — один из этих счетов. Проценты начисляются ежемесячно по дням (Дт 91.02 Кт 66.02/67.02, статья interest — строка 2330). Платёж привязывается POST /loans/payments (purpose: disbursement, principal, interest). В балансе часть 67 к погашению в течение 12 месяцев после отчётной даты переносится в строку 1510 (п. 19 ФСБУ 4/2023), проводкой Дт 67 Кт 66 это не делается. Займы траншами (financial-statements.md §3.5). POST /loans принимает ещё: counterpartyId (карточка справочника; имя и ИНН берутся из неё, без неё — counterpartyName / counterpartyInn текстом), onDemand (до востребования: dueOn не нужен и хранится как null, тело на 66.01, проценты идут до фактического возврата), interestFree (беспроцентный: ставка сохраняется 0, проводок по процентам нет; annualRateBp: 0 без этого флага — 400), byTranches и tranches: [{ date, amountKopecks }] — выдачи, вписанные руками. При byTranches principalKopecks — сумма по договору (лимит), а тело — фактические выдачи: привязанные платежи disbursement и ещё не закрытые ими tranches; у каждой выдачи счёт 66.01 / 67.01 по сроку от её даты, проценты — на фактический остаток по дням. Привязка платежа disbursement закрывает самые ранние вписанные выдачи на свою сумму. Без byTranches и без tranches заём считается одной суммой с issuedOn, как раньше. POST /loans/tranches { loanId, tranches } заменяет список вписанных выдач (409 у займа одной суммой; 400, если выдача раньше даты договора, не раньше срока или вместе с привязанными платежами больше суммы по договору). В ответе у займа: counterpartyId, onDemand, interestFree, byTranches, tranches, disbursements (date, amountKopecks, transactionId | null, account) и суммы на сегодня — disbursedKopecks, repaidKopecks, outstandingKopecks, accruedInterestKopecks, paidInterestKopecks, unpaidInterestKopecks; dueOn может быть null. Привязка и предупреждения займов. POST /loans/payments проверяет направление денег: выдача полученного займа и возврат выданного — поступление, выдача нашего займа и возврат полученного — списание, иначе 400 с текстом. Выдачи платежами сверх суммы по договору — не запрет: ответ { items, candidates, warnings: string[] }, в warnings — «выдано больше суммы договора», остаток вписанной руками выдачи, которую платёж закрыл не целиком, и пометка об НДФЛ при привязке уплаты процентов. У займа в ответе warnings: string[] (то же постоянно, пока условие верно) и ndflAgent: boolean — полученный заём под проценты от человека (карточка вида individual, ИНН из 12 цифр без карточки ИП или участник-физлицо из устава без ИНН): компания — налоговый агент по НДФЛ (ст. 226 НК); это только пометка, налог не считается и не проводится. POST /loans/tranches при платежах сверх лимита разрешает уменьшать вписанные выдачи. POST /loans/payments/remove { loanId, transactionId } отвязывает платёж (404, если он к этому займу не привязан): проводка по нему снова строится по виду платежа. Ответ — { items, candidates, warnings: string[] }. Вписанные выдачи, которые платёж закрыл при привязке, возвращаются в tranches ровно такими, какими он их закрыл (закрытая часть — к оставшейся выдаче на ту же дату): при привязке это запоминается, и у платежа в карточке payments[].restoresTranches: [{ date, amountKopecks }] — что вернёт отвязка ([] — ничего). У выдач займа траншами, привязанных до этого, restoresTranches: null: отвязка работает как раньше, а в warnings — «вписанная выдача не восстановлена — впишите её заново, если деньги были». Вид платежа отвязка не меняет: если он остаётся видом «заём» (loan_in / loan_return_out — журнал держит его на 66.01, loan_out / loan_return_in — на 58.03, без карточки срок и беспроцентность неизвестны) и ни к одному займу не привязан, в warnings ответа и в warnings карточки, от которой его отвязали, — «платёж остался займом в учёте — привяжите его к займу или смените вид платежа»; из карточки предупреждение уходит, когда платёж снова привязан или у него сменили вид. Аналитика «контрагент» у привязанных платежей и у начисленных процентов — одна, из карточки займа (ИНН, без него — name:<название>), а не из выписки. Основные средства (/assets): карточка с инвентарным номером, датой ввода, стоимостью и сроком. Ввод Дт 01 Кт 08 (или 10); ниже лимита учётной политики — сразу Дт 26/44 Кт 08/10. Линейная амортизация Дт 26/44 Кт 02 с месяца, следующего за вводом. Выбытие — остаточная стоимость в 91.02. Налоговый учёт ОС при УСН «доходы минус расходы» (п. 3 ст. 346.16) не ведётся. Товары (/inventory): приход с количеством и суммой, себестоимость по средней, списание Дт 90.02 Кт 41. Строка продажи сопоставляется с товаром по артикулу или названию; не сопоставилась — предупреждение и ручное списание. Входящий документ на 41 без позиций становится одной позицией «товары» количеством 1. Дивиденды (/dividends): решение делит сумму по долям участников (Дт 84.01 Кт 75.02). НДФЛ физлицам — 13 % до 2,4 млн ₽ дивидендов за год и 15 % сверху (п. 1.1 ст. 224 НК), Дт 75.02 Кт 68.01; срок уплаты — п. 6 ст. 226, в ответе ndflDueOn. Организации — налог на прибыль у источника (п. 3 ст. 275): 0 %, если на дату решения доля не меньше 50 % и владение не меньше 365 дней (пп. 1 п. 3 ст. 284), иначе 13 % (пп. 2). Дата владения — поле участника ownedFrom. Проводка Дт 75.02 Кт 68.04 появляется после POST /dividends/pay (paidOn); к выплате нетто. Срок уплаты — п. 4 ст. 287 (profitTaxDueOn), расчёт КНД 1151006 лист 03 — ст. 289 (profitTaxReportDueOn). ООО на УСН тоже агент. Выплата видом dividends с ИНН участника закрывает 75.02. Предупреждения (чистые активы, нераспределённая прибыль) не блокируют проведение. GET /api/accounting/account-card?account&from&to — карточка счёта: сальдо на начало, проводки с сальдо после каждой и сальдо на конец; account — субсчёт или синтетический счёт со всеми субсчетами, аналитика (counterparty, bankAccount, contract, employee, transit, item) — в строке запроса и сужает карточку до одной строки ОСВ; проводки страницами (offset, limit до 500; total — всего), итоги — по всему периоду. GET /api/accounting/trial-balance/xlsx?from&to — ОСВ в Excel с итоговой строкой и проверкой Σ Дт = Σ Кт. GET /api/accounting/journal/xlsx?from&to&account — журнал проводок; GET /api/accounting/account-card/xlsx?account&from&to и GET /api/accounting/account-analysis/xlsx?account&from&to — карточка счёта (проводки с сальдо после каждой, итоги за период) и анализ счёта (обороты по корреспондирующим счетам) одним файлом, без постраничности; аналитика — в строке запроса, как у карточки на экране. Во всех четырёх файлах суммы — числами в рублях, даты проводок — датами Excel (формат «дд.мм.гггг»: работают фильтр и сортировка), предупреждения журнала — те же, что на экране, — строками «Внимание: …» под шапкой; в шапке — название компании с ИНН/КПП, период и дата формирования (по часовому поясу компании). План счетов — CHART_OF_ACCOUNTS в @ooo/shared. Без права «Зарплаты и ПДн сотрудников» в ОСВ, её выгрузке, журнале, карточке и их выгрузках нет аналитики employee (счета 70 и 71 — одной строкой, свёрнутым сальдо; обороты те же), фильтр карточки по employee не действует, а примечание ввода остатков по сотруднику заменено на «Ввод остатков» (access-policy.md §6.3).

Метод Путь Доступ
GET /api/accounting/trial-balance JWT; раздел «Бухгалтерия» — просмотр
GET /api/accounting/journal JWT; раздел «Бухгалтерия» — просмотр
GET /api/accounting/account-card JWT; раздел «Бухгалтерия» — просмотр
GET /api/accounting/trial-balance/xlsx JWT; раздел «Бухгалтерия» — просмотр, право «Массовые выгрузки»
GET /api/accounting/journal/xlsx JWT; раздел «Бухгалтерия» — просмотр, право «Массовые выгрузки»
GET /api/accounting/account-card/xlsx JWT; раздел «Бухгалтерия» — просмотр, право «Массовые выгрузки»
GET /api/accounting/account-analysis/xlsx JWT; раздел «Бухгалтерия» — просмотр, право «Массовые выгрузки»
GET /api/accounting/policy JWT; раздел «Бухгалтерия» — просмотр
POST /api/accounting/policy JWT; раздел «Бухгалтерия» — изменение
GET /api/accounting/bank-balances JWT; раздел «Бухгалтерия» — просмотр
POST /api/accounting/bank-balances JWT; раздел «Бухгалтерия» — изменение
GET /api/accounting/loans JWT; раздел «Бухгалтерия» — просмотр
POST /api/accounting/loans JWT; раздел «Бухгалтерия» — изменение
POST /api/accounting/loans/payments JWT; раздел «Бухгалтерия» — изменение
POST /api/accounting/loans/payments/remove JWT; раздел «Бухгалтерия» — изменение
POST /api/accounting/loans/tranches JWT; раздел «Бухгалтерия» — изменение
POST /api/accounting/loans/employee JWT; раздел «Бухгалтерия» — изменение, право «Зарплаты и ПДн сотрудников»
POST /api/accounting/loans/remove JWT; раздел «Бухгалтерия» — управление
GET /api/accounting/assets JWT; раздел «Бухгалтерия» — просмотр
POST /api/accounting/assets JWT; раздел «Бухгалтерия» — изменение
POST /api/accounting/assets/dispose JWT; раздел «Бухгалтерия» — изменение
POST /api/accounting/assets/remove JWT; раздел «Бухгалтерия» — управление
GET /api/accounting/inventory JWT; раздел «Бухгалтерия» — просмотр
POST /api/accounting/inventory JWT; раздел «Бухгалтерия» — изменение
POST /api/accounting/inventory/receipts JWT; раздел «Бухгалтерия» — изменение
POST /api/accounting/inventory/issues JWT; раздел «Бухгалтерия» — изменение
POST /api/accounting/inventory/remove JWT; раздел «Бухгалтерия» — управление
GET /api/accounting/dividends JWT; раздел «Бухгалтерия» — просмотр
POST /api/accounting/dividends JWT; раздел «Бухгалтерия» — изменение
POST /api/accounting/dividends/pay JWT; раздел «Бухгалтерия» — изменение
POST /api/accounting/dividends/remove JWT; раздел «Бухгалтерия» — управление
GET /api/accounting/charter JWT; раздел «Бухгалтерия» — просмотр
POST /api/accounting/charter JWT; раздел «Бухгалтерия» — изменение
GET /api/accounting/opening JWT; раздел «Бухгалтерия» — просмотр
POST /api/accounting/opening JWT; раздел «Бухгалтерия» — изменение, право «Закрывать период»
DELETE /api/accounting/opening JWT; раздел «Бухгалтерия» — изменение, право «Закрывать период»
GET /api/accounting/years JWT; раздел «Бухгалтерия» — просмотр
POST /api/accounting/years/close JWT; раздел «Бухгалтерия» — изменение, право «Закрывать период»
POST /api/accounting/years/reopen JWT; раздел «Бухгалтерия» — изменение, право «Закрывать период»
GET /api/accounting/statements JWT; раздел «Бухгалтерия» — просмотр
GET /api/accounting/statements/pdf JWT; раздел «Бухгалтерия» — просмотр
GET /api/accounting/statements/xml JWT; раздел «Бухгалтерия» — просмотр, право «Сдавать отчётность»
GET /api/accounting/statements/notes JWT; раздел «Бухгалтерия» — просмотр, право «Сдавать отчётность»

trial-balance?from=ГГГГ-ММ-ДД&to=ГГГГ-ММ-ДД — ОСВ: по каждому счёту сальдо на начало, обороты и сальдо на конец (debitKopecks / creditKopecks), развёрнутые по аналитике в rows (контрагент, расчётный счёт, договор, операция в пути); balanced — Σ Дт = Σ Кт по всем колонкам; warnings — операции без вида, переводы между своими счетами без пары. journal — те же даты и необязательный account («60» — со всеми субсчетами, «60.02» — только он); счёт не из плана — 400.

policy — учётная политика с даты effectiveFrom: метод признания (method: accrual — по отгрузке, cash — по оплате), счёт затрат (costAccount: 26 или 44), мелкие расходы без документа по оплате (expensesOnPayment), куда относить комиссии банка и посредников (commissions: cost или other — 91.02), лимит ОС (fixedAssetLimitKopecks) и право на упрощённые способы бухучёта (simplifiedMethods). GET — current и periods; пока политики нет, действует политика по умолчанию (id: null, с 2000-01-01). POST заводит период или правит период на ту же дату; кассовый метод без simplifiedMethods — 400.

bank-balances?date=ГГГГ-ММ-ДД (по умолчанию сегодня) — по каждому расчётному счёту расчётный остаток (сальдо 51 на конец дня), остаток банка на эту или ближайшую раннюю дату (bankDate), разница на дату остатка банка и status: ok, mismatch, unknown (остатка банка нет). POST с bankAccountId, date, balanceKopecks — остаток из выписки на конец дня; повторный ввод на ту же дату — правка; счёт чужой компании — 404.

charter — уставный капитал (charterCapitalKopecks), дата регистрации и участники (name, inn, sharePercent; сумма долей — 100 %, иначе 400). POST сохраняет карточку целиком. В ответе у участника вклад по доле и сколько внесено и осталось — по сальдо 75.01 в журнале (оплата вида «Взнос учредителя» с ИНН участника гасит его долг), и posting: journal — Дт 75.01 Кт 80 датой регистрации, opening — ООО зарегистрировано раньше даты начала учёта, капитал вводится в остатки, none — нет суммы или даты.

opening — ввод остатков на дату начала учёта (date): строки lines (section — balances, documents, turnovers; account, side, amountKopecks, аналитика bankAccountId, counterpartyInn, counterpartyName, contractId, employeeId, note) и прошлогодний баланс priorYear для сравнительной графы. Каждая строка — проводка против счёта 000 последним днём перед датой начала; события раньше даты начала в журнал не входят. POST сохраняет всё целиком; сальдо 000 не ноль — 400 с differenceKopecks; обороты 90/91/99 при старте с 1 января — 400; ссылка на чужой счёт, договор или сотрудника — 404. GET — строки, итоги и проверки checks (ok или warn): остаток 51 против остатка банка на день перед началом, уставный капитал в остатках, операции до даты начала, прошлогодний баланс. Без права «Зарплаты и ПДн сотрудников» строки счетов 70, 71 и 73 и любые строки с employeeId свёрнуты по счёту и стороне: employeeId пуст, примечание и имя контрагента строки очищены, сумма счёта та же. DELETE убирает ввод остатков (нет — 404).

years?year=ГГГГ — закрытие бухгалтерского года: closed (кто и когда закрыл, чистая прибыль, число проводок в снимке), previewNetProfitKopecks — результат, который даст реформация, и history закрытий. POST /years/close с year проводит реформацию на 31.12 (субсчета 90 и 91 → 90.09 и 91.09 → 99 → 84.02) и сохраняет проводки года и ОСВ снимком; с 1 января следующего года 84.02 переносится на 84.01. Закрыть можно только закончившийся год (409), по порядку — незакрытый прошлый год с доходами и расходами даёт 409, повторное закрытие — 409. Журнал за закрытый год читает снимок: правка операций прошлого года его не меняет, а даёт предупреждение со списком счетов. POST /years/reopen с year и reason открывает год заново (не закрыт — 404, закрыт следующий — 409).

statements?year=ГГГГ — упрощённая бухгалтерская отчётность (ФСБУ 4/2023, приложение 9): баланс на 31 декабря отчётного, предыдущего и позапрошлого года (balanceDates) и ОФР за отчётный и предыдущий год (pnlYears). У строки code — код показателя с наибольшим значением в группе, kopecks и thousands по графам (null — данных нет, в форме прочерк), expense — расход в скобках. Графа прошлого года — прошлогодний баланс из ввода остатков, если он есть, иначе журнал (priorSource). checks — 1600 = 1700, рост нераспределённой прибыли = чистая прибыль − дивиденды, деньги против выписки, закрыт ли год; incomplete — баннер, если есть остатки ОС, материалов или товаров; notes — пояснения по п. 54 ФСБУ 4/2023. Предприниматель отчётность не сдаёт — 409. statements/pdf — печатная форма. statements/xml — файл для ГИР БО по формату 5.05 (приказ ФНС ЕД-1-1/326@, КНД 0710096) в windows-1251 с именем NO_BOUPR_A_K_O_ГГГГММДД_N.xml; statements/notes — PDF пояснений с именем, на которое ссылается XML того же дня. Без КПП, кода налоговой, адреса или ФИО руководителя в настройках — 409 со списком missing.

Входящие закрывающие документы библиотеки (/api/documents) — регистр расходов: у них vatKopecks, expenseAccount (26, 44, 91.02, 08, 10, 41; пусто — счёт затрат учётной политики) и payment — оплата по журналу (paidKopecks, status: paid, partial, unpaid). Проводки: Дт статья Кт 60.01 датой документа (на ОСНО НДС — Дт 19), зачёт аванса поставщику Дт 60.01 Кт 60.02 — сначала привязанными к документу оплатами, затем по поставщику.

Ключи приложений — /api/api-clients

Ключ показывается ОДИН раз — при создании. В базе лежит только хеш и последние 4 символа.

Метод Путь Доступ
GET /api/api-clients JWT; право «Интеграции»
POST /api/api-clients JWT; право «Интеграции»
PATCH /api/api-clients/:id JWT; право «Интеграции»
POST /api/api-clients/:id/revoke JWT; право «Интеграции»

Аутентификация — /api/auth

Регистрация своей компании — POST /signup: { email, password, name, companyName, inn, termsAccepted, pdConsent }. Две отметки формы, обе — только true: termsAccepted — человек принял Оферту и Условия использования, pdConsent — дал отдельное согласие на обработку персональных данных по Политике (ст. 9 152-ФЗ). Без любой из них или с другим значением — 400 («Нужно принять Оферту и Условия использования» или «Нужно согласие на обработку персональных данных»), учётка не создаётся. При успехе на человеке сохраняются дата согласия, версия политики, IP и User-Agent (pdConsent*), а в legalAcceptances — редакции всех четырёх документов (offer, terms, pd-consent, privacy) с той же датой, IP и User-Agent. Редакции ставит сервер — те, что опубликованы сейчас (/legal/<slug>). Заводит учётную запись, компанию в статусе pending и членство-администратора, отвечает 201 с сессией (token, user) и mailSent — ушло ли письмо со ссылкой подтверждения. Компания работает только после двух шагов: человек подтверждает почту (POST /verify-email/:token по ссылке из письма — тогда компания попадает в очередь оператора) и оператор её одобряет. До этого данные компании закрыты и для владельца (см. «Компания запроса»). Отказы: ИНН с неверной контрольной суммой — 400 с fields.inn; компания с этим ИНН уже есть — 409 «Компания уже зарегистрирована — попросите её администратора пригласить вас»; почта занята — 409 с fields.email; больше 10 регистраций с одного IP за сутки — 429. Неподтверждённая регистрация ИНН не держит: через 3 дня (срок ссылки) его можно зарегистрировать заново. POST /verify-email/resend — новая ссылка, не чаще раза в минуту (429), прежняя перестаёт работать.

В проде регистрация открыта, только когда настроена почта (SMTP_HOST и MAIL_FROM): без письма почту не подтвердить. Иначе — 503, а GET /setup отдаёт signupOpen: false.

/register открыт, только пока в базе нет пользователей, — так заводится первый администратор (он же оператор платформы) при развёртывании с нуля; GET /setup говорит, открыт ли он (registrationOpen). Дальше POST /register отвечает 403, а сотрудников в компанию добавляет её администратор. POST /password — сменить свой пароль, с проверкой текущего.

POST /forgot-password — письмо со ссылкой для смены пароля. Тело { email }; ответ всегда одинаковый { ok: true }, есть такая почта или нет (иначе форма перебирает адреса). Отказы: 503 в проде без настроенной почты (как регистрация); 429 — чаще раза в минуту на один адрес или больше 10 запросов с одного IP за сутки (лимиты одинаковы для любой почты). POST /reset-password — новый пароль по ссылке из письма: { token, newPassword }, пароль — от 6 символов. Ссылка одноразовая и живёт час; неверная или использованная — 404, просроченная — 410. Вместе со сменой ставится passwordChangedAt: выданные до него JWT перестают действовать — все прежние сессии разрываются.

/login, /register, /signup и /me отдают профиль: memberships — компании, роль в каждой и статус компании (tenantStatus, для отказа и приостановки — tenantStatusNote), emailVerified — подтверждена ли почта, tenantId и role — активная компания (из X-Tenant-Id, а без него единственная; null, если компаний несколько). Права — по политике доступа (docs/research/access-policy.md): у каждого членства profile (full, accountant, secretary, sales, hr, observer), overrides — число поправок к профилю и access — итоговые права { profile, sections: { <раздел>: none|view|edit|manage }, actions: [...] }; access верхнего уровня — права в активной компании (null, если она не выбрана). role — переходное поле, вычисляется из профиля. Отказ по правам — 403 { error, code: "access_denied" }. /me, /password и /verify-email/resend работают без выбора компании.

Метод Путь Доступ
POST /api/auth/login без авторизации
GET /api/auth/me JWT
POST /api/auth/password JWT
GET /api/auth/setup без авторизации
POST /api/auth/register без авторизации
POST /api/auth/signup без авторизации
POST /api/auth/verify-email/:token без авторизации (токен из письма)
POST /api/auth/verify-email/resend JWT
POST /api/auth/forgot-password без авторизации
POST /api/auth/reset-password без авторизации (токен из письма)

Банковские счета — /api/bank-accounts

Токен доступа к банку шифруется AES-256-GCM на BANK_TOKEN_KEY и наружу не отдаётся. POST /:id/sync тянет выписку и попутно заводит новых контрагентов. PUT /:id/token меняет токен у подключённого счёта без потери истории: новый токен сначала проверяется в банке, при отказе прежний остаётся. Номер счёта уникален в пределах компании — второе подключение того же счёта даёт 409; счёт с загруженными операциями не удаляется. Токен Т-Банка привязан к IP: если банк его отверг (401/403), к тексту банка добавляется подсказка проверить, что IP «Потока» (egressIps из /api/onboarding) разрешён для токена в Т-Бизнесе. У каждого счёта free — бесплатный ли он по тарифу (см. «Тариф»). PATCH /:id правит то, чего нет в API банка: своё название счёта, название и город банка (bankCity — печатается в шапке счёта: «АО «ТБанк» г. Москва») и корсчёт. Номер счёта и токен так не меняются: номер — это другой счёт, для токена есть PUT /:id/token.

Метод Путь Доступ
GET /api/bank-accounts JWT; раздел «Банковские счета» — просмотр
POST /api/bank-accounts JWT; раздел «Банковские счета» — изменение
PATCH /api/bank-accounts/:id JWT; раздел «Банковские счета» — изменение
DELETE /api/bank-accounts/:id JWT; раздел «Банковские счета» — управление
POST /api/bank-accounts/:id/archive JWT; раздел «Банковские счета» — изменение
POST /api/bank-accounts/:id/sync JWT; раздел «Банковские счета» — изменение
PUT /api/bank-accounts/:id/token JWT; раздел «Банковские счета» — изменение

Тариф — /api/billing

Первый подключённый расчётный счёт — бесплатно, каждый следующий — 1000 ₽ в месяц, оплата вперёд счётом из самого «Потока». Бесплатные — самые ранние по дате подключения: удалили бесплатный — бесплатным становится следующий. GET /api/billing — сводка компании: accounts, paidAccounts, monthlyKopecks, freeAccountIds, paidUntil (оплачено по), dueOn (срок выставленного счёта за тариф) и status:

Пока счёт за тариф просрочен (overdue и blocked), компания не расширяется: POST /api/bank-accounts, POST /api/api-clients, POST /api/webhooks и POST /api/invites отвечают 402 с объяснением. Счета, платежи, сверка и отчёты работают как обычно. Владельцу приходят два письма на счёт: в день срока и в день остановки платных счетов.

Счёт за тариф выставляет сам «Поток» — из своей компании в «Потоке» (PLATFORM_TENANT_ID), в первую неделю месяца, на месяц вперёд: количество — платные счета, подключённые до начала месяца (подключённый в середине месяца работает сразу, а в счёт попадает со следующего первого числа). Счёт уходит владельцу письмом с PDF; оплату отмечает обычная сверка по выписке. В сводке — unpaidInvoice: номер, сумма и сколько уже пришло; PDF — GET /api/billing/invoices/:id/pdf (только своей компании, чужой — 404). Частичная оплата долг не закрывает; отвязанный платёж и аннулирование счёта пересчитывают долг.

Метод Путь Доступ
GET /api/billing JWT; любой член компании
GET /api/billing/invoices/:id/pdf JWT; любой член компании

Категории — /api/categories

Метод Путь Доступ
GET /api/categories JWT; раздел «Справочники» — просмотр
POST /api/categories JWT; раздел «Справочники» — изменение
DELETE /api/categories/:id JWT; раздел «Справочники» — управление
POST /api/categories/:id/archive JWT; раздел «Справочники» — изменение
PATCH /api/categories/:id JWT; раздел «Справочники» — изменение

Договоры и оферты — /api/contracts

Основание платежа и счёта: договор, оферта или счёт-оферта. У оферты бумаги нет — есть ссылка на условия на сайте поставщика и дата акцепта (оплаты).

Метод Путь Доступ
GET /api/contracts JWT; раздел «Договоры» — просмотр
POST /api/contracts JWT; раздел «Договоры» — изменение
GET /api/contracts/:id JWT; раздел «Договоры» — просмотр
PATCH /api/contracts/:id JWT; раздел «Договоры» — изменение
DELETE /api/contracts/:id JWT; раздел «Договоры» — управление
POST /api/contracts/:id/archive JWT; раздел «Договоры» — изменение
GET /api/contracts/:id/periods JWT; раздел «Договоры» — просмотр
PATCH /api/contracts/:id/periods/:key JWT; раздел «Договоры» — изменение
POST /api/contracts/:id/document JWT; раздел «Договоры» — изменение
GET /api/contracts/:id/related JWT; раздел «Договоры» — просмотр
GET /api/contracts/:id/tree JWT; раздел «Договоры» — просмотр
GET /api/contracts/:id/events JWT; раздел «Договоры» — просмотр
GET /api/contracts/:id/snapshots JWT; раздел «Договоры» — просмотр
POST /api/contracts/:id/snapshot JWT; раздел «Договоры» — изменение
POST /api/contracts/match JWT; раздел «Договоры» — изменение

Фильтры списка: kind, side (buyer — мы покупатель, supplier — мы поставщик), counterpartyId, q, active (1 — действующие, 0 — закончившиеся).

В каждом ответе есть totals: paidKopecks — сколько денег прошло по договору, closedKopecks — на сколько получено закрывающих документов (акты, накладные, УПД, чеки), плюс счётчики платежей, документов и счетов. Признак active считается на лету: по сроку, автопролонгации и дате закрытия.

Периодические договоры. У договора есть periodicity (once, monthly, quarterly, yearly), periodAmountKopecks — ожидаемая сумма за период и settlementDay — до какого числа платят. GET /api/contracts/:id/periods возвращает ряд периодов по сегодняшний день: ключ (2026-09, 2026-Q3, 2026), границы, ожидаемая сумма, что заплачено, чем закрыто и статус (current, ok, unpaid, unclosed, unpaid_unclosed). Ряд считается из периодичности и срока — в базе он не хранится; автопролонгация продолжает его за срок действия, будущих периодов нет.

Сумма периода правится отдельно — PATCH /api/contracts/:id/periods/:key с amountKopecks и note: по аренде с коммуналкой каждый месяц своя, а сумма в договоре остаётся умолчанием. amountKopecks: null снимает уточнение. Уточнить можно только существующий период — иначе 404.

Пробелы по всем периодическим договорам сразу — GET /api/reports/contract-periods (параметры from, to): месяцы без платежа, без закрывающего документа или без обоих. Текущий период в отчёт не попадает — он ещё идёт.

Основание проставляется у платежа (PATCH /api/transactions/:id с contractId) и у счёта (contractId при создании и обновлении). Договор, по которому уже есть платежи или счета, не удаляется — его закрывают датой (endedOn), иначе деньги остались бы без объяснения.

Подбор договора к платежу. Платежу без основания договор проставляется сам — после загрузки выписки, после сохранения договора и по POST /api/contracts/match (ответ: matched — скольким проставлен, ambiguous — скольким подходят несколько). Правила по порядку: договор счёта, который платёж оплатил; договор контрагента с номером из назначения («по договору № 12/26 от 01.03.2026»); единственный действующий договор контрагента с нужной стороны. Контрагент определяется по ИНН платежа. Если назван номер, которого нет, или подходят несколько договоров, платёж остаётся без основания, а договоры приходят в contractCandidates строк GET /api/reports/kudir?year — книга учёта доходов и расходов на УСН за год (форма по приказу ФНС от 07.11.2023 № ЕА-7-3/816@; year по умолчанию — прошлый): раздел I по кварталам (quarters[].rows — № п/п, дата, реквизиты документа, содержание, incomeKopecks без предъявленного НДС, expenseKopecks — только на «Доходах минус расходы»), итоги за квартал и нарастающим итогом, справка к разделу I (reference, строки 010–041). Строки — из налогового регистра, итоги совпадают с оценкой налога. /kudir/pdf — печатная форма, /kudir/xlsx — то же в Excel. На ОСНО — 409. Без права «Зарплаты и ПДн сотрудников» в строках выплат сотрудникам (вид с ролью salary или получатель — сотрудник) содержание заменяется на «Выплата сотруднику» или «Поступление от сотрудника»: и в JSON, и в PDF, и в XLSX. Суммы и итоги те же. С правом книга полная — её и сдают.

GET /api/reports/usn-declaration?year&correction — декларация по УСН за год (КНД 1152017, приказ ФНС от 26.11.2025 № ЕД-7-3/1017@, формат 5.09): разделы 1.1/1.2 и 2.1.1/2.2 в полных рублях, строки 150–162 (290–320) для ИП, авансы по уведомлениям закрытых кварталов (closedAdvances), blockers — без чего файл не примут (ИНН, КПП, ОКТМО, код налоговой, подписант), warnings — что проверить. /usn-declaration/pdf — показатели для проверки глазами, /usn-declaration/xml — файл NO_USN_…xml в windows-1251 для личного кабинета или оператора; пока blockers не пуст — 409 со списком.

GET /api/reports/personified?month=ГГГГ-ММ — персонифицированные сведения о физических лицах (КНД 1151162, приказ ФНС от 29.09.2022 № ЕД-7-11/878@, формат 5.01): ежемесячная отчётность в ФНС не позднее 25-го числа (п. 7 ст. 431 НК). Строится из закрытого месяца зарплаты; в ответе — число сотрудников, итог выплат, blockers (нет закрытия, пустые СНИЛС/ФИО, реквизиты), warnings и filingNotRequired — не null за последний месяц квартала: тогда сведения не сдаются (данные входят в РСВ с разделой 3, письмо ФНС от 28.03.2023 № БС-4-11/3700@), и панель скрывает кнопки отправки, оставляя только выгрузку XML. /personified/file — файл NO_PERSSVFL_…xml в windows-1251 для личного кабинета или оператора; СНИЛС и ФИО внутри, поэтому выгрузка требует права «Сдавать отчётность» и «Зарплаты и ПДн сотрудников», а без готовых данных — 409 со списком.

GET /api/reports/rsv?year&quarter — расчёт по страховым взносам (КНД 1151111, приказ ФНС от 29.09.2022 № ЕД-7-11/878@ в редакции от 04.02.2026 № ЕД-1-11/67@, формат 5.08): квартальная отчётность до 25-го числа. Строится по закрытым месяцам с начала расчётного периода — с января или, для новой компании, с даты регистрации из устава (CharterCapital; ООО с 03.08.2026 — январь–июль не закрываются), до конца квартала (сводные графы — нарастающим итогом); в превью — сотрудники, выплаты и взносы, blockers (незакрытые месяцы, льготный тариф МСП/IT, пустые кадровые поля) и warnings. /rsv/file — файл NO_RASCHSV_…xml в windows-1251: сводная часть по единому тарифу 01, Раздел 3 с датами рождения, гражданством (ОКСМ), полом и паспортами — выгрузка под теми же правами, что и у персонифицированных сведений; без готовых данных 409.

GET /api/reports/ndfl6?year&quarter — расчёт 6-НДФЛ (КНД 1151100, приказ ФНС от 19.09.2023 № ЕД-7-11/649@, формат 5.05, файл NO_NDFL6.2_…): квартальная отчётность до 25-го числа, Раздел 1 по КБК и Раздел 2 по ставкам шкалы нарастающим итогом с января (или с даты регистрации — для новой компании, как в RSV) по закрытым месяцам, шесть сроков перечисления — полосы удержания последнего квартала из обязательств (нужен график выплат). /ndfl6/file — файл в windows-1251; квартальные расчёты ПДн не содержат, поэтому право «Зарплаты и ПДн сотрудников» не требуется; годовой расчёт (с приложением 1 «Персонифицированные сведения») пока не формируется — 409. Без готовых данных — тоже 409 со списком.

GET /api/reports/efs1?year&quarter — ЕФС-1, раздел 2 «Взносы на травматизм» в СФР (приказы СФР от 17.11.2025 № 1462/1463): квартально до 25-го числа. Численности и суммы (выплаты, база, исчислено) — по ТипСуммыЗаПериоды: с начала года (или с даты регистрации — для новой компании), на конец предыдущего периода и три последних месяца календарного квартала — месяц до регистрации приходит с нулём; закрываются месяцы с даты регистрации, среднесписочная — среднее по ним же (правила Росстата для созданных в течение года). Тариф из налогового профиля. Нужен регистрационный номер СФР в настройках. /efs1/file — UTF-8-файл; в разделе нет ПДн, поэтому достаточно права «Сдавать отчётность». Блок результатов СОУТ (РПО) по умолчанию — все места 1 класса, панель предупреждает.

GET /api/reports/missing-docs. У платежа contractSource: auto — подобрано, manual — указано через PATCH (в том числе contractId: null); указанное вручную подбор не переписывает.

Документ договора. Скан, ссылка в ЭДО и подпись договора — документ библиотеки вида contract, на который указывает documentId договора. POST /api/contracts/:id/document заводит его, если его нет, и возвращает. Номер, дата, контрагент и сумма у договора и документа общие: правка с любой стороны переносится на другую. У такого документа в ответах /api/documents заполнен contractId. Документ вида contract, заведённый в библиотеке без связи, находит договор с тем же контрагентом, номером и датой, а если такого нет — заводит карточку.

Снимок условий оферты. Страницу оферты поставщик меняет молча, поэтому сервер сохраняет её на дату акцепта: POST /api/contracts/:id/snapshot — по кнопке, и сам — при первой оплате, привязанной к оферте (подбор договора или PATCH /api/transactions/:id с contractId), фоном: оплата не ждёт чужой сайт и не падает из-за него. Снимок — документ библиотеки вида offer_snapshot (раньше — other с меткой «Снимок оферты»; такие переводятся миграцией при старте), связью с договором (и с платежом у автоснимка) и PDF-файлом: адрес, итоговый адрес после перенаправлений, время получения, HTTP-статус, SHA-256 ответа и текст страницы; исходный HTML с <base href> вложен в PDF файлом offer.html. Снимок только у оферты со ссылкой на условия (kind не contract, offerUrl задан) — иначе 400. Сервер ходит по адресу так же осторожно, как по вебхуку: в проде только https и никаких внутренних адресов, каждое перенаправление проверяется заново; таймаут 10 с, не больше 5 МБ; скрипты не выполняются, картинки и стили не запрашиваются. Неудача — 400 (адрес), 413 (больше 5 МБ), 415 (по ссылке не страница), 502 (страница не открылась); в теле кроме error — тот же ответ, что у GET.

GET /api/contracts/:id/events → ContractEventDto[], свежие сверху (до 500): журнал договора — at, type, actorName (человек или «Saby»), auto, fields (у updated — какие поля поменялись: number, date, counterpartyId, subject…), documentId, documentLabel, note и text — запись простым языком. Типы: created (заведён, в том числе из документа ЭДО), updated (правка, только реально изменённые поля), edo_merged / edo_split (объединён с документом из ЭДО вручную или автоматически / разъединён), edo_state (сменился статус ЭДО «Самого договора» — синхронизация), document_linked / document_unlinked (связь документа с договором), archived / unarchived. Журнал пишет сервер, изменить его нельзя.

GET /api/contracts/:id/snapshots → {offerUrl, snapshots, log}: снимки (документы библиотеки, свежие сверху) и журнал попыток — последние 20, свежие сверху: at, trigger (manual — кнопкой, payment — при оплате), ok, error, documentId, transactionId. Неудачный автоснимок виден только здесь и в логе сервера; следующая оплата попробует снова, после удачного автоснимка — нет.

GET /api/contracts/:id/related — всё, что связано с договором, для вкладок карточки одним запросом: invoices (счета с contractId: номер, дата, статус, сумма, оплачено), payments (платежи с contractId), totals (invoicedKopecks — выставлено без черновиков и аннулированных, paidKopecks — оплачено по ним, debtKopecks — остаток), sales (наши документы продажи по договору, с documentId экземпляра), incoming (входящие закрывающие документы со связью с договором), letters — переписка: via: "subject" — договор указан в письме, "document" — письмом пришёл или ушёл документ договора, "counterparty" — прочие письма контрагента (до 50, в конце списка). counts — числа на вкладках: money (счета и платежи), closing (документы продажи и входящие закрывающие), letters (без прочих писем контрагента).

GET /api/contracts/:id/tree → {contractDocument, appendices, annexes, snapshots, other, count}: документы, чей дом — этот договор (owner = contract:<id>), в порядке чтения. contractDocument — сам договор (Contract.documentId, может быть null); appendices — приложения и спецификации по номеру (числа внутри номера сравниваются как числа: 1, 2, 10); annexes — допсоглашения по дате, затем по номеру; snapshots — снимки оферты, свежие сверху; other — прочее по дате. Узел — {document, children}: children — вложения документа (parentId), например приложение к допсоглашению. Вложение, чей родитель пропал, показывается в корне. count — сколько документов в доме, кроме самого договора. Документы, только связанные с договором (акты, счета-фактуры), в дерево не входят — они в GET /api/documents?link=contract:<id>.

Контрагенты — /api/counterparties

Единственный модуль, кроме счетов, доступный по ключу приложения: контрагента надо завести, прежде чем выставить ему счёт.

Реквизиты проверяются по типу (kind): у юрлица ИНН — 10 цифр, ОГРН — 13; у ИП ИНН — 12 цифр, ОГРНИП — 15, КПП нет; у физлица нет ни КПП, ни ОГРН. Контрольные суммы ИНН и ОГРН — по алгоритмам ФНС, буквы КПП приводятся к заглавным. Пустое поле — не ошибка. Ошибка — 400 с error и ошибками по полям в fields: {"error": "…", "fields": {"inn": "…"}}. При правке проверяются только переданные поля; смена типа на ИП или физлицо убирает КПП (и ОГРН у физлица).

Метод Путь Доступ
GET /api/counterparties ключ приложения, скоуп counterparties:read
POST /api/counterparties ключ приложения, скоуп counterparties:write
DELETE /api/counterparties/:id ключ приложения, скоуп counterparties:write; человеку ещё раздел — управление
POST /api/counterparties/:id/archive ключ приложения, скоуп counterparties:write
PATCH /api/counterparties/:id ключ приложения, скоуп counterparties:write

Библиотека документов — /api/documents

Карточка документа: вид, номер, дата, контрагент, файлы и связи. Документ — не файл: бумажный оригинал, скан и документ в ЭДО описываются одной карточкой (source: paper, file, edo).

Метод Путь Доступ
GET /api/documents JWT; раздел «Документы» — просмотр
GET /api/documents/counts JWT; раздел «Документы» — просмотр
POST /api/documents JWT; раздел «Документы» — изменение
GET /api/documents/:id JWT; раздел «Документы» — просмотр
PATCH /api/documents/:id JWT; раздел «Документы» — изменение
DELETE /api/documents/:id JWT; раздел «Документы» — управление
POST /api/documents/:id/files JWT; раздел «Документы» — изменение
DELETE /api/documents/:id/files/:fileId JWT; раздел «Документы» — изменение
POST /api/documents/:id/links JWT; раздел «Документы» — изменение
DELETE /api/documents/:id/links/:type/:refId JWT; раздел «Документы» — изменение
POST /api/documents/:id/original JWT; раздел «Документы» — изменение
POST /api/documents/recognize JWT; раздел «Документы» — изменение
POST /api/documents/:id/recognize JWT; раздел «Документы» — изменение
POST /api/documents/:id/ocr/retry JWT; раздел «Документы» — изменение
POST /api/documents/:id/edo/copy JWT; раздел «Документы» — изменение
POST /api/documents/:id/archive JWT; раздел «Документы» — изменение
POST /api/documents/:id/unarchive JWT; раздел «Документы» — изменение
POST /api/documents/:id/keep JWT; раздел «Документы» — изменение
POST /api/documents/:id/merge-into-contract JWT; разделы «Документы» и «Договоры» — изменение
POST /api/documents/:id/create-contract JWT; разделы «Документы» и «Договоры» — изменение
POST /api/documents/:id/split-edo JWT; разделы «Документы» и «Договоры» — изменение
GET /api/documents/people JWT; раздел «Документы» — просмотр; право «Конфиденциальные документы»
GET /api/documents/:id/access-log JWT; раздел «Документы» — просмотр; право «Конфиденциальные документы» или «Управление пользователями»

Конфиденциальные документы (docs/research/access-policy.md §6.4). Поле confidential — документ видят держатели права «Конфиденциальные документы» (docs.confidential) и назначенные люди (accessUserIds — id пользователей компании, список кандидатов — GET /api/documents/people). Остальным такого документа нет нигде: ни в списке и поиске (в том числе по тексту скана), ни в счётчиках, ни в дереве и связях договора, ни в связях других документов; по id — 404, как у несуществующего; его файлы по /api/files/:id* — тоже 404. Если отметку не передать, она ставится по умолчанию: закрыты кадровые (kind: hr) и документы с домом у сотрудника; тот же документ, ставший кадровым при правке, закрывается сам. Менять отметку и назначенных может только держатель права (иначе 403 с code: access_denied); назначенные приходят в ответе только ему, остальным — []. Человек без права, заведший закрытый документ, становится назначенным — иначе документ пропал бы у него из рук. Назначить можно только члена компании (404).

Журнал доступа (§6.5). Каждое открытие карточки конфиденциального документа (GET /api/documents/:id) и его файла (GET /api/files/:id/content) записывается до ответа: { at, lastAt, repeats, userId, userName, action: view|download, documentId, documentTitle, fileId, fileName }. Повторы одного человека по тому же документу, файлу и действию в пределах 10 минут склеиваются (repeats, lastAt). Записи хранятся 3 года и не правятся. GET /api/documents/:id/access-log?cursor&limit — журнал документа страницами { items, nextCursor }, свежие сверху; общий журнал компании — GET /api/access-log.

Фильтры списка: kind (через запятую), direction, source, counterpartyId, tag, from, to (даты в виде ГГГГ-ММ-ДД), q (номер, название, описание, контрагент), link в виде <тип>:<id>, awaiting=1 (ждут подписи), payment (unpaid, partial, paid — входящие закрывающие документы с суммой по оплате в журнале; «документ без оплаты» — payment=unpaid) и limit (до 500). Входящие закрывающие документы с суммой — регистр расходов: поля vatKopecks (не больше суммы, иначе 400), expenseAccount и payment описаны в разделе «Бухгалтерия».

Папка «Разобрать» и архив. Карточка, которая, похоже, не нужна, попадает в «Разобрать» сама (вычисляется фильтром, без фоновой задачи), и в карточке видно, почему: triageReasons — annulled («Аннулирован в ЭДО»), rejected («Отклонён в ЭДО»), duplicate (подпись дубликата — triageDuplicateLabel: «Дубль документа № … от …»), fetch_failed («Файл не скачался», в том числе карточка «не удалось разобрать» из ЭДО) или stale_draft («Черновик без данных больше 30 дней»). Решение человека — отметкой, не правкой карточки: POST /api/documents/:id/keep («Оставить» — документ больше не предлагается; новый переход ЭДО в аннулирован/отклонён снова его предложит) и POST /api/documents/:id/archive («В архив» — обратимо: POST /api/documents/:id/unarchive; не в архиве — 409). Пока решение не принято, документ ждёт в списке GET /api/documents?triage=1, счётчик — triage в ответе /api/documents/counts; убранные — в ?archived=1, счётчик archived. Обычный список (GET /api/documents), вкладки и счётчики, поиск, «Проверить» и «Чего не хватает» идут без архивных и без нерешённых из «Разобрать»; учёта, налогов, книг НДС, закрытия периодов, договоров и сверки они не двигают: архивный и нерешённый — до решения, аннулированный и отклонённый в ЭДО — никогда, даже после «Оставить». Удаление — как раньше, вручную, без автоматизма.

Договоры из ЭДО. Карточка вида «Договор» из ЭДО (source: edo, без своего договора) — не сам договор «Потока», а его носитель в системе ЭДО; её можно объединить с уже заведённым договором или завести по ней договор. POST /api/documents/:id/merge-into-contract {contractId, confirm?, dryRun?} — файлы (PDF, XML, подписи) и ЭДО-поля (edo.*, включая packageNumber — номер пакета Saby для справки) переезжают в документ «Сам договор» выбранного договора (он становится source: edo), карточка удаляется, а ключ вложения (system, externalId, attachmentId) остаётся на «Самом договоре»: следующее событие ЭДО не заводит карточку заново, а обновляет статус договора. Другие вложения того же документа Saby (пакет «договор + приложение») присоединяются тем же объединением: их ключи — в edo.mergedAttachmentIds, отдельных карточек по ним синхронизация не заводит. Ссылки на карточку (вложения parentId, связи document, edo.duplicateDocumentId) переходят на «Сам договор». Реквизиты договора (номер, дата, стороны, сумма) не меняются. dryRun: true → 200 {changes, warnings}: что изменится и о чём предупредить, без изменений. Предупреждения (warnings: [{code, text}]): other_counterparty (ИНН документа и договора разные), archived_contract (договор в архиве), annulled (документ аннулирован или отклонён в ЭДО), replace_edo (у «Самого договора» уже есть объединённый документ ЭДО: он снова станет отдельной карточкой — аннулированный сразу в архиве, — а его место займёт этот), confidential_card (закрытая карточка закроет и «Сам договор»: доступ — её назначенным людям). Есть предупреждения и нет confirm: true — 409 {error, warnings, needsConfirm: true}. Закрытый «Сам договор», которого человек не видит, — 403 без реквизитов; закрытая карточка при открытом «Самом договоре» — только с правом docs.confidential (иначе 403). «Сам договор» — сам документ из ЭДО (договор заведён по карточке, снимка нет) — 409: подменять его нельзя. Ответ — документ «Сам договор» с edoMerge: {at, byName, auto, cards}. POST /api/documents/:id/split-edo («Разъединить», :id — «Сам договор») → {document, restoredIds}: карточки из ЭДО восстанавливаются с теми же id, своими файлами и ЭДО-полями (статус — нынешний), «Сам договор» — как до первого объединения (источник, ЭДО-поля, подпись, файлы, связи, закрытость), ссылки возвращаются. Разъединённую карточку (edo.splitAt) автосопоставление больше не трогает. Не объединялся — 409. ЭДО-поля документа, связанного с вложением Saby (edo.attachmentId), меняют только синхронизация, объединение и разъединение: PATCH /documents/:id с source/edo их не трогает (у ручной ссылки ЭДО — как прежде). POST /api/documents/:id/create-contract {side?, counterpartyId?, number?, date?, confirm?} — заводит договор из карточки: номер — из документа (номер пакета Saby номером договора не становится — пусто, если человек не передал number), дата — из имени файла «от ДД.ММ.ГГГГ» или карточки, сторона по направлению (входящий — «мы покупатель», можно передать side), контрагент по ИНН из ЭДО (нет карточки контрагента — заводится по ИНН и названию, 12 цифр — ИП; несколько карточек с этим ИНН — 400 с fields.counterpartyId, выбрать явно); карточка становится «Самим договором» (дом и связь договора, contractId в ответе), без копий. Предупреждения has_candidates (похожий договор уже заведён) и annulled — 409 {warnings} без confirm: true. Синхронизация Saby при заведении карточки-договора сама объединяет её, только когда уверенность высокая: номер взят из документа (XML или имя файла, edo.numberSource: xml/file; package — номер пакета, ему не верим), он сильный (не короче четырёх значимых знаков, не «б/н», не дата), вложение — сам договор (не «Дополнительное соглашение», «Приложение», «Спецификация», «Акт» по имени файла), ровно один договор «Потока» с тем же ИНН контрагента и совпадающим номером (сравнение нормализует №, пробелы, _ / - \), он не в архиве и у его «Самого договора» ещё нет документа ЭДО. Иначе карточка остаётся с подсказкой edo.contractHint «Похоже на договор № … — объединить?» (у «Самого договора» аннулированный документ — «заменить им прежний документ ЭДО?»); подсказка снимается, когда кандидатов не стало. Аннулированные и отклонённые в ЭДО не объединяются автоматически никогда.

У входящего УПД (kind: upd) и счёта-фактуры (kind: vat_invoice) есть purchaseInvoice — реестр для книги покупок. У остальных документов и пока блок не заполнен поле null; null в PATCH снимает документ с реестра. Номер (number) хранится как напечатан, без своей нормализации. Дальше: date, sellerInn, sellerKpp (обязателен при ИНН из 10 цифр и пуст при ИНН из 12), sellerName, receivedOn, acceptedOn (дата принятия на учёт, графа 8), rates — строки {rate, baseKopecks, vatKopecks} по ставкам 22, 20, 10, 7, 5 и 0. НДС строки равен базе × ставка (допуск 1 коп.; у 0% налог строго 0). Если строки есть, их сумма и НДС сходятся с amountKopecks и vatKopecks. updStatus — 1 или 2 у УПД и null у отдельного счёта-фактуры; статус 2 в книгу покупок не ставится. operationCode — 01, 02 или 18. deductionQuarter — ГГГГ-Qn или пусто (тогда квартал acceptedOn): не раньше квартала принятия и не позже квартала даты «плюс три года» (п. 1.1 ст. 172 НК), один квартал на документ. Пустой квартал обязателен, если forTaxableOperations равен false (по умолчанию true, п. 2 ст. 171 НК) или статус УПД — 2. Исправление — пара correctionNumber и correctionDate (строка 1а). Корректировочный — пара ksfNumber и ksfDate плюс исходный originalNumber и originalDate (строка 1б). Чужой вид, направление или несходящиеся суммы — 400 с fields. Оплата поставщику — прежняя связь transaction, отдельного поля нет. Код 02 (аванс поставщику) в реестре хранится, в книгу покупок этой задачей не проводится.

Виды (kind) разложены по группам (DOCUMENT_KIND_GROUPS в packages/shared): договорные — contract, annex, appendix (приложение), specification, offer_snapshot (снимок условий оферты, делает сервер); первичные — act, waybill, upd, vat_invoice, invoice, receipt, reconciliation (акт сверки); внутренние — power_of_attorney, order, statute, decision (решение или протокол участников), hr; переписка — letter; прочее — other. Фильтр group (contractual, primary, internal, correspondence, other) отбирает виды группы.

Дом документа — owner: {type, id, label} | null: объект, где документ живёт (contract, sales_document, letter, employee, counterparty). Дом — одна из связей, главная: при создании или правке owner: {type, id} связь того же типа появляется сама; убрали связь дома — дома больше нет. Не указан при создании — сервер ставит его по виду и единственной связи: допсоглашение, приложение, спецификация, снимок оферты и сам договор с одним договором — в договоре; письмо с одним письмом — в письме; кадровый с одним сотрудником — у сотрудника. owner: null — без дома (внутренние документы компании и входящие первичные). Объекта дома нет в вашей компании — 404. ownerRole — main (сам договор или экземпляр документа продажи), attachment (вложение дома) или null. salesDocumentId — у экземпляра нашего документа продажи. parentId — родитель в том же доме (приложение к допсоглашению): вложенность в один уровень, родитель из другого дома, сам вложенный или сам документ — 400. Удалили родителя или он ушёл из дома — вложения остаются в доме, в корне. Фильтр owner=<тип>:<id> — документы дома.

Вкладки архива — view: incoming (входящие первичные: акт, накладная, УПД, счёт-фактура, счёт, чек — регистр расходов), outgoing (те же виды, исходящие), contractual (договорные и акты сверки), internal (внутренние), reporting (платёжные поручения, декларации и отчёты, квитанции налоговой, требования), all. Вкладка задаёт выборку, прочие фильтры её сужают, но не расширяют: view=incoming&kind=contract пуст; неизвестная вкладка — 400 с перечнем допустимых. unhomed=1 — документы без дома и без единой связи, которые не являются документом договора. В outgoing не попадают экземпляры документов продажи — сами документы продажи отдаёт /api/sales-documents.

С view или cursor ответ — страница архива { items, nextCursor, total }: limit — от 1 до 200, по умолчанию 50; total — сколько документов под фильтром всего; следующая страница — тот же запрос с cursor=<nextCursor>, nextCursor: null — страница последняя. Порядок — по дате и id убыванием, курсор — позиция последней строки, поэтому документ, заведённый между страницами, не сдвигает следующую. Испорченный курсор — 400. Без view и cursor ответ — массив, как раньше (limit до 500).

GET /api/documents/counts?from&to — счётчики вкладок за период: incoming, outgoing, contractual, internal, reporting, all — по { total, attention }, где attention — сколько требует действия (входящие без суммы или оплаченные не полностью; исходящие и договорные, которые ждут подписи; у исходящих в счёт идут и документы продажи, кроме аннулированных; у отчётности — требования налоговой с их сроком ответа); control — { awaiting, unhomed, recognized, missing, noOriginal, overdueOriginals, attention }: документы, которые ждут подписи, непривязанные, распознанные из файлов, но не подтверждённые человеком (считаются по всему архиву: платёжка или декларация на «Входящих» не числятся, а проверять их надо), расходы без закрывающего документа (как withoutDocument.count в GET /api/reports/missing-docs), где оригинал не отмечено и выданные, которые не вернули в срок. attention — бейдж вкладки: число РАЗНЫХ документов, попавших хоть в одну проверку (одна карточка бывает в нескольких сразу, и сумма счётчиков считала бы её несколько раз), плюс расходы без документа. Рядом triage — сколько ждёт решения в «Разобрать», archived — сколько убрано в архив; в «Контроль» они не суммируются.

Отметка о подписи exchange — та же, что у документов продажи: mode (edo, paper, none), sentAt, signedAt, url, note. По умолчанию документ из ЭДО подписывается в ЭДО, бумажный договор, доп. соглашение, приложение, спецификация, акт, накладная, УПД и акт сверки — живой подписью, остальным подпись не нужна.

Связь (links) — с счётом (invoice), платежом (transaction), сотрудником (employee), проектом (project), письмом журнала (letter), закрытием периода (period — квитанция или протокол приёма отчётности), обязательством по налогу (obligation — налоговая платёжка с конкретным КБК, привязывает человек в карточке по подсказке) или другим документом (document). Подпись связи проставляется сервером: объект должен существовать в вашей компании, иначе 404. Связь с обязательством информационная: суммы ЕНС всегда считаются по выписке, платёжка их не меняет.

Файл прикладывается уже загруженным — сначала POST /api/files, потом POST /api/documents/:id/files с { "fileId": "..." }. Отвязанный файл, если он больше не лежит ни в одном документе, удаляется вместе с содержимым; удалить его напрямую, пока он приложен, нельзя — 409.

Где оригинал. original документа — null (не отмечено) или {state, locationId, locationPath, note, holderName, employeeId, dueDate, letterId, letterLabel, url, label, overdue, changedAt, changedByName}. state: office (у нас — место хранения и примечание «файл 12»), counterparty (у контрагента), sent (отправлен, можно указать письмо журнала letterId), scan_only (только скан), issued (выдан сотруднику — holderName или employeeId обязательны, dueDate — вернуть до), edo (электронный, в ЭДО — url, по умолчанию ссылка документа). label — «где» одной строкой: «Офис / Шкаф 1 / П-3, файл 12», «выдан Иванову до 01.10.2026», «в ЭДО»; overdue — выдан и не возвращён в срок. Документу из ЭДО состояние edo ставится само. POST /api/documents/:id/original с {state, …} (или {state: null} — снять отметку) меняет состояние, прежнее уходит в originalHistory (свежие сверху, приходит в карточке документа). Место, сотрудник или письмо не из вашей компании — 400. Фильтры списка: noOriginal=1 — где оригинал не отмечено (кроме документов из ЭДО), overdue=1 — выданные и не возвращённые в срок, location=<id> — лежат в месте или любом вложенном в него. Выданный и не возвращённый к сроку оригинал — письмо администраторам компании, одно на выдачу (задача original-reminders, с 9 утра по времени компании).

Распознавание и поиск по тексту. Каждый загруженный файл встаёт в очередь распознавания. У PDF текстовый слой снимается сразу (страница короче 32 знаков считается сканом); сканы и фото распознаёт отдельный воркер Tesseract 5 (rus+eng), не больше 50 страниц на файл. Облачного распознавания нет — файлы не покидают сервер. ocrStatus документа — по его файлам: pending (в очереди), processing (распознаётся), done (текст в поиске), failed (не получилось за три попытки: пауза 5 мин, 1 ч, 6 ч), skipped (распознаны не все страницы), null — файлов нет. POST /api/documents/:id/ocr/retry возвращает в очередь файлы документа в failed или skipped — попытки с нуля; повторять нечего — 409.

Распознавание карточки из файла. По тексту файла (слой или OCR) POST /api/documents/recognize с { "fileId": "…" } и POST /api/documents/:id/recognize определяют вид документа (счёт-фактура, УПД статуса 1/2, корректировочный счёт-фактура, акт, ТОРГ-12, счёт на оплату, а также платёжное поручение — в том числе налоговое с полями 101–110, кассовый чек ККТ по 54-ФЗ, налоговая и иная отчётность по КНД, квитанции и извещения налоговой, требования и решения о блокировке счёта) и возвращают { fileId, status, ocrStatus, result, suggestions }. status: recognized — разбор готов; no_text — текст есть, но знакомой формы нет; pending/processing — текст ещё распознаётся (веб ждёт и спрашивает снова); failed/skipped — очередь не справилась. result — поля с уверенностью 0..100 (number, date, sellerName, sellerInn, sellerKpp, buyerInn, buyerKpp, суммы, строки позиций, ставки, строки 5 и 5а), самопроверка арифметики и предупреждения («документ выставлен не вам», суммы не сходятся). У платёжек, чеков и отчётности в result приходят ещё knd (форма по КНД), periodKey (отчётный период «2026-Q3» или год), correctionNumber, dueDate (срок ответа требования) и extras — подписанные поля вне карточки (КБК, ОКТМО, УИН, поля 101–110 платёжки, ФД/ФН/ФП чека, результат квитанции); каждое со своей уверенностью. Черновик распознанного не участвует в учёте: новые виды не входят в закрывающие, проводок по ним нет до подтверждения. suggestions — подсказки по данным компании: counterpartyId (контрагент с ИНН продавца или получателя платёжки; нет — counterpartyCreateByInn: true, реквизиты предложит GET /api/requisites/by-inn), payment — исходящий платёж тому же поставщику на ту же сумму в окне ±60 дней (для платёжки — операция выписки в окне ±3 дня, по ИНН получателя и номеру п/п), ещё не привязанный к другому документу, expenseAccount — статья по прошлым документам поставщика или по учётной политике; для отчётности — period (закрытый налоговый период тем же ключом) и для квитанций — report (единственная загруженная декларация за два месяца). У налоговой платёжки (есть КБК) контрагент по ИНН казначейства не предлагается, а в tax приходит подсказка по КБК: ЕНП (ens: true) — ensCoverage с тем, что пополнение покроет по расчёту ЕНС (covers — ближайшие обязательства; если операция выписки с платёжкой уже есть, alreadyInLedger: true — расчёт её уже учитывает); конкретный КБК — candidates (обязательства того же вида, сначала период из поля 107, у каждого match: exact/under/over; кандидатов несколько — выбор за человеком, автопривязки нет); пени и штраф — только note («обязательство не закрывают»); плательщик чужой (notOurPayer) — подсказки привязки нет. Привязка ставится человеком: POST /api/documents/:id/links с {"type": "obligation", "id": "<обязательство>"}. Карточка, созданная по результату, создаётся с recognitionDraft: true и числится в списке «Проверить» (recognized=1 в GET /api/documents, счётчик control.recognized в /counts); правка карточки с recognitionConfirmed: true в PATCH /api/documents/:id снимает признак.

Поиск q — полнотекстовый по карточке (вид, номер, название, описание, метки, контрагент, сумма в рублях) и тексту файлов с русской морфологией («договора» находит «Договор»), плюс прежнее совпадение подстроки в карточке (номер «12-А»). Результаты — одна страница до 200 документов по релевантности, без курсора (nextCursor: null); фильтры вкладки действуют поверх поиска. У каждого найденного — match: {snippet, source, page, fileName}: ±80 знаков вокруг совпадения, source — card или file, page — номер страницы файла. Искать можно только среди своих документов: текст чужой компании не находится.

POST /api/documents/:id/edo/copy скачивает документ по ссылке ЭДО и сохраняет копию у себя (edo.copyFileId): ссылка живёт, пока жив договор с оператором, а документы нужны все пять лет хранения. Если по ссылке приходит страница входа, а не документ, — 415 с просьбой приложить файл вручную; 409 — копия уже сохранена.

Места хранения оригиналов — /api/document-locations

Помещение → шкаф → полка → папка: уровни и названия свободные.

Метод Путь Доступ
GET /api/document-locations JWT; раздел «Документы» — просмотр
POST /api/document-locations JWT; раздел «Документы» — изменение
PATCH /api/document-locations/:id JWT; раздел «Документы» — изменение
DELETE /api/document-locations/:id JWT; раздел «Документы» — управление

GET — все места плоским списком по пути: {id, name, parentId, note, path, documents, total}, где documents — документов прямо в месте, total — с вложенными. POST — {name, parentId?, note?}. PATCH — {name?, parentId?, note?}: смена parentId переносит место со всем содержимым (документы ссылаются на место и переезжают вместе с ним); внутрь самого себя — 400. DELETE — только пустое место, иначе 409. Что лежит в месте — GET /api/documents?location=<id>.

Сотрудники — /api/employees

Метод Путь Доступ
GET /api/employees JWT; раздел «Сотрудники» — просмотр
GET /api/employees/options JWT; раздел «Сотрудники» — просмотр
POST /api/employees JWT; раздел «Сотрудники» — изменение, право «Зарплаты и ПДн сотрудников»
DELETE /api/employees/:id JWT; раздел «Сотрудники» — управление
POST /api/employees/:id/archive JWT; раздел «Сотрудники» — изменение
PATCH /api/employees/:id JWT; раздел «Сотрудники» — изменение
POST /api/employees/:id/bonuses JWT; раздел «Сотрудники» — изменение, право «Зарплаты и ПДн сотрудников»
DELETE /api/employees/:id/bonuses/:bonusId JWT; раздел «Сотрудники» — управление, право «Зарплаты и ПДн сотрудников»

ПДн сотрудников (право hr.pii, access-policy.md §6.2). Без права сотрудник отдаётся без полей EMPLOYEE_PII_FIELDS из @ooo/shared — rateMilli, salaryFromPosition, employmentBasis, salaryKopecks, monthlyBonusKopecks, bonuses, startedOn, endedOn — и с pii: false: {id, name, position, positionId, isHead, active, pii}. Поля не пустые, а отсутствуют. С правом — всё и pii: true. В PATCH без права можно менять имя, должность и active; поле из списка ПДн в теле — 403 с кодом access_denied. Принять сотрудника (POST) и менять премии без права нельзя: в них оклад.

GET /api/employees/options — список для выбора (привязка документа, получатель): [{id, name, position, active}], без ПДн, одинаковый для всех, кому открыт раздел.

Файлы компании — /api/files

Сканы документов, печать и подпись, копии документов из ЭДО. Файл лежит в S3-совместимом хранилище, а ссылок наружу не выдаётся: содержимое отдаёт сам API, проверив сессию и компанию.

Загрузка — multipart/form-data, поле file. Тип определяется по содержимому, а не по расширению: PDF, JPEG, PNG, TIFF. Предел одного файла — 25 МБ, место на компанию — 5 ГБ по умолчанию (меняется оператором).

Метод Путь Доступ
POST /api/files JWT; раздел «Документы» — изменение
GET /api/files/usage JWT; раздел «Документы» — просмотр
GET /api/files/:id JWT; раздел «Документы» — просмотр
GET /api/files/:id/content JWT; раздел «Документы» — просмотр
DELETE /api/files/:id JWT; раздел «Документы» — управление

GET /api/files/:id/content отдаёт сам файл; с ?download=1 — вложением (Content-Disposition: attachment). Повторная загрузка того же содержимого отвечает 200 и прежним файлом вместо 201: место оно занимает один раз. Отдельные коды: 413 — файл больше предела, 415 — такой тип не принимаем, 507 — место компании закончилось, 503 — хранилище не настроено.

Файл, лежащий в конфиденциальном документе, виден тому, кому виден хоть один документ, где он лежит; остальным /api/files/:id, /content и DELETE отвечают 404. Такой файл отдаётся с Cache-Control: private, no-store, и каждое открытие пишется в журнал доступа. Повторная загрузка того же содержимого человеком, которому прежний файл не виден, заводит отдельный файл (201), а не отдаёт скрытый.

Серии счетов — /api/invoice-series

Метод Путь Доступ
GET /api/invoice-series JWT; раздел «Справочники» — просмотр
POST /api/invoice-series JWT; раздел «Справочники» — изменение
DELETE /api/invoice-series/:id JWT; раздел «Справочники» — управление
POST /api/invoice-series/:id/archive JWT; раздел «Справочники» — изменение
PATCH /api/invoice-series/:id JWT; раздел «Справочники» — изменение

Счета на оплату — /api/invoices

Основной модуль. Доступен по ключу приложения со скоупами invoices:read / invoices:write.

send-email отправляет выставленный счёт письмом с PDF во вложении: тело { to, subject?, message? }, to — строка через запятую или массив, до 5 адресов. Без темы и текста уходит письмо по шаблону. Письмо идёт с адреса «Потока» от имени компании, ответ — на почту компании из настроек. Черновик и аннулированный счёт — 409, почта не настроена — 503, почтовый сервер отказал — 502. Каждая отправка пишется в журнал счёта.

Метод Путь Доступ
GET /api/invoices ключ приложения, скоуп invoices:read
POST /api/invoices ключ приложения, скоуп invoices:write
DELETE /api/invoices/:id ключ приложения, скоуп invoices:write; человеку ещё раздел — управление
GET /api/invoices/:id ключ приложения, скоуп invoices:read
PATCH /api/invoices/:id ключ приложения, скоуп invoices:write
GET /api/invoices/:id/bank-status ключ приложения, скоуп invoices:read
POST /api/invoices/:id/cancel ключ приложения, скоуп invoices:write; человеку ещё право «Выставлять счета и документы», раздел — управление
GET /api/invoices/:id/documents ключ приложения, скоуп invoices:read
GET /api/invoices/:id/history ключ приложения, скоуп invoices:read
POST /api/invoices/:id/issue ключ приложения, скоуп invoices:write; человеку ещё право «Выставлять счета и документы»
GET /api/invoices/:id/payment-status ключ приложения, скоуп invoices:read
POST /api/invoices/:id/payments ключ приложения, скоуп invoices:write
DELETE /api/invoices/:id/payments/:transactionId ключ приложения, скоуп invoices:write
GET /api/invoices/:id/pdf ключ приложения, скоуп invoices:read
GET /api/invoices/:id/print ключ приложения, скоуп invoices:read
POST /api/invoices/:id/send-email ключ приложения, скоуп invoices:write; человеку ещё право «Отправлять письма»
POST /api/invoices/:id/send-to-bank ключ приложения, скоуп invoices:write; человеку ещё право «Выставлять счета и документы»
GET /api/invoices/payments/unmatched ключ приложения, скоуп invoices:read
POST /api/invoices/reconcile ключ приложения, скоуп invoices:write

Журнал доступа — /api/access-log

Кто и когда открывал конфиденциальные документы и их файлы — по всей компании (docs/research/access-policy.md §6.5). Записи те же, что у GET /api/documents/:id/access-log; из API журнал не правится и не удаляется.

Метод Путь Доступ
GET /api/access-log JWT; право «Управление пользователями»

Фильтры: userId, documentId, from, to (ГГГГ-ММ-ДД), страницы — cursor и limit (до 200). Ответ — { items, nextCursor }, свежие сверху. Неверный id, дата или курсор — 400.

Приглашения — /api/invites

Сотрудника приглашают по одноразовой ссылке: придёт письмо, человек сам задаст пароль — администратор его не знает. Ссылка действует 7 дней и срабатывает один раз; повторная отправка (resend) выдаёт новую ссылку, старая перестаёт работать; новое приглашение на ту же почту отзывает прежнее. Если письмо не ушло (почта не настроена), ответ содержит link — передать её самому; иначе link — null.

По ссылке (/accept/:token, без входа): GET — компания, почта, роль и есть ли уже учётная запись; POST — принять. Тело всегда содержит pdConsent: true — отдельное согласие на обработку ПДн (ст. 9 152-ФЗ). Без него 400, ссылка остаётся. Сервер пишет дату, версию политики, IP и User-Agent. Если согласия на учётке ещё не было, заполняются поля pdConsent*; повторная отметка дописывается в legalAcceptances (согласие и политика) и не затирает первую дату. Оферту приглашённый не акцептует. Новый человек передаёт ещё name и password и сразу получает сессию. Если учётная запись с этой почтой уже есть, принять можно только войдя в неё (Authorization: Bearer) — компания добавится, пароль не меняется. Использованная, истёкшая или отозванная ссылка — 410.

Метод Путь Доступ
GET /api/invites JWT; право «Управление пользователями»
POST /api/invites JWT; право «Управление пользователями»
POST /api/invites/:id/resend JWT; право «Управление пользователями»
DELETE /api/invites/:id JWT; право «Управление пользователями»
GET /api/invites/accept/:token без авторизации (токен из ссылки)
POST /api/invites/accept/:token без авторизации; для существующей учётной записи — её JWT

Заявки с лендинга — /api/leads

POST публичный — его дёргает форма заявки. Тело: { email, inn, source?, termsAccepted, pdConsent }. Обе отметки — только true, как при регистрации: termsAccepted — Оферта и Условия использования, pdConsent — согласие на обработку ПДн (ст. 9 152-ФЗ); иначе 400, заявка не пишется. При успехе сохраняются дата согласия, версия политики, IP и User-Agent, а в legalAcceptances — редакции всех четырёх документов. Ограничение: 20 заявок с одного IP в сутки; повтор с тем же email обновляет заявку (и согласие), а не плодит вторую. Всё остальное — только оператору платформы: это его воронка, а не компании. На самом лендинге форма заявки заменена переходом на регистрацию компании: согласие человек отмечает там. Этот POST по-прежнему требует согласия, если заявку присылают напрямую.

У заявки status: new → tenant_created (в tenantId — заведённая компания) или rejected. Обработать можно только новую заявку; второй раз — 409 «Заявка уже обработана».

POST /api/leads/:id/create-tenant — компания из заявки, это и есть одобрение. Тело: { name, inn?, ownerEmail? } — ИНН и почта владельца по умолчанию из заявки, оператор может поправить опечатку. Компания заводится активной и без членов: владельцу уходит приглашение с ролью admin (как в /api/invites — пароль он задаст сам по одноразовой ссылке). Ответ 201: { lead, tenant: { id, name }, invite: { invite, mailSent, link } }; link не пустой, только если письмо не ушло — тогда её передают сами.

Отказы: ИНН с неверной контрольной суммой — 400 с fields.inn; компания с таким ИНН уже есть — 409 «Компания с этим ИНН уже подключена». Сбой посреди заведения откатывает всё и возвращает заявке статус new — повтор проходит.

Метод Путь Доступ
GET /api/leads JWT, оператор платформы
POST /api/leads без авторизации
POST /api/leads/:id/create-tenant JWT, оператор платформы
POST /api/leads/:id/reject JWT, оператор платформы

Журналы писем — /api/letters

Входящие и исходящие письма компании — как в делопроизводстве: регистрационный номер, дата, корреспондент, тема, исполнитель и срок. Нумерация у входящих и исходящих своя, сквозная и с января заново: Вх-2026/0001, Исх-2026/0001. Номер выдаёт серия журнала — атомарно, и если письмо не сохранилось, номер возвращается в серию. Серии журналов в /api/invoice-series не видны.

Метод Путь Доступ
GET /api/letters JWT; раздел «Письма» — просмотр
GET /api/letters/correspondents JWT; раздел «Письма» — просмотр
GET /api/letters/executors JWT; раздел «Письма» — просмотр
GET /api/letters/:id JWT; раздел «Письма» — просмотр
POST /api/letters JWT; раздел «Письма» — изменение
PATCH /api/letters/:id JWT; раздел «Письма» — изменение
POST /api/letters/:id/register JWT; раздел «Письма» — изменение
POST /api/letters/:id/status JWT; раздел «Письма» — изменение

POST /api/letters принимает direction (incoming, outgoing), date (ГГГГ-ММ-ДД — дата получения входящего или регистрации исходящего), correspondent ({ counterpartyId, name } — контрагент из справочника или просто название; название хранится снимком), subject, channel (post, registered_post, courier, email, edo, in_person) и необязательные summary, executorId (участник компании), dueOn, signerName, trackNumber, senderNumber, senderDate, resolution, inReplyToId, subjects — о чём письмо: [{ type, id }], type — contract, invoice или project (заменяет список целиком; в ответе — с подписью label; объекта нет в вашей компании — 400). Фильтр журнала subject=<тип>:<id> — письма о договоре, счёте или проекте. Входящее регистрируется сразу — получает номер и статус new; дата должна быть в текущем году: задним годом письмо не регистрируется (409). Исходящее создаётся черновиком draft без номера, номер выдаёт POST /api/letters/:id/register (статус registered).

PATCH принимает те же поля без direction. Аннулированное письмо не правится (409); дату зарегистрированного письма нельзя перенести в другой год — номер выдан за год (409).

POST /api/letters/:id/status — { status, date?, note? } по таблице переходов LETTER_TRANSITIONS. Исходящее: registered → sent → delivered → answered → closed; входящее: new → in_progress → answered → closed; аннулировать (cancelled) можно всё, кроме закрытого, — с обязательной причиной в note (иначе 400). sent и delivered ставят sentOn/deliveredOn — date или сегодня. Недопустимый переход и registered через этот маршрут — 409. Аннулирование номер не возвращает.

Фильтры списка: direction (обязателен), status, executorId, counterpartyId, from, to, q (номер, корреспондент, тема, исходящий номер отправителя), overdue=1 — срок прошёл, а письмо не в answered, closed, cancelled; senderNumber — точный исходящий номер отправителя без учёта регистра и пробелов по краям, для проверки дубля входящего. Новые сверху, не больше 500.

GET /api/letters/correspondents?q= — до 20 подсказок: контрагенты по названию или началу ИНН и названия, уже встречавшиеся в письмах без контрагента.

GET /api/letters/executors — кого можно поставить исполнителем: участники компании, [{ id, name }] без почты и ролей. Доступен всем ролям, в отличие от /api/users; executorId при сохранении проверяется по этому же списку.

Ошибки проверки — 400 { error, fields }, где fields — сообщение у поля.

Первые шаги компании — /api/onboarding

Чек-лист на главной кабинета: что осталось новой компании до первого счёта. Шаг пройден по данным, а не галочкой: requisites — в настройках есть название и ИНН (без них выставление отвечает 409), bankAccount — подключён расчётный счёт, project — заведён проект (необязательный шаг), invoice — есть счёт не в черновике. complete — пройдены все обязательные.

egressIps — исходящие IP сервера «Потока» (из EGRESS_IPS). Токен Т-Банка привязан к IP: до ввода токена эти адреса добавляют в разрешённые для него в Т-Бизнесе. Пустой список — адрес не настроен.

{
  "steps": [
    { "key": "requisites", "done": true, "optional": false },
    { "key": "bankAccount", "done": false, "optional": false },
    { "key": "project", "done": false, "optional": true },
    { "key": "invoice", "done": false, "optional": false }
  ],
  "complete": false,
  "egressIps": ["203.0.113.10"]
}
Метод Путь Доступ
GET /api/onboarding JWT; раздел «Главная» — просмотр

Виды платежей — /api/payment-kinds

canSettleInvoice определяет, может ли доход такого вида быть оплатой счёта: от него зависит, уйдёт ли поступление из очереди неразнесённых.

Метод Путь Доступ
GET /api/payment-kinds JWT; раздел «Справочники» — просмотр
POST /api/payment-kinds JWT; раздел «Справочники» — изменение
DELETE /api/payment-kinds/:id JWT; раздел «Справочники» — управление
POST /api/payment-kinds/:id/archive JWT; раздел «Справочники» — изменение
PATCH /api/payment-kinds/:id JWT; раздел «Справочники» — изменение

Зарплатные ведомости — /api/payroll-registers

Все маршруты требуют JWT, прав money.pay и hr.pii: GET — просмотра раздела «Сотрудники», POST — изменения раздела.

Метод Маршрут Назначение
POST /api/payroll-registers/preview Проверить ведомость: `{period: "ГГГГ-ММ", kind: "advance"
POST /api/payroll-registers Создать проверенную ведомость: те же поля и sourceHash из превью
GET /api/payroll-registers/:id/export Экспорт поручений сотрудникам, формат 1С 1.03, windows-1251
POST /api/payroll-registers/:id/confirm Подтвердить выплату сотруднику: {employeeId, transactionId}

Реквизиты сотрудника задаются в payout карточки, шифруются в базе и доступны только с hr.pii. Ответы ведомости показывают маску счёта. Окончательная ведомость требует закрытого месяца. В обоих POST необязательный bankAccountId выбирает активный счёт компании; превью показывает банк и маску счёта плательщика. Чужой или архивный счёт — 404. Повторное создание по тому же источнику возвращает существующую ведомость; изменённый расчёт и устаревший экспорт блокируются. После нового превью rebuild: true в POST создаёт новую неизменяемую ревизию и помечает прежнюю stale. Подтверждённую ведомость пересоздавать нельзя. Экспорт не считается выплатой. Подтверждение проверяет сумму, счёт, получателя, вид и месяц банковской операции и сохраняет индивидуальные суммы дохода и НДФЛ.

Налоговые платёжные поручения — /api/payment-orders

Все маршруты требуют JWT и права money.pay: GET — просмотра раздела «Налоги к уплате», POST и PUT — изменения раздела.

Метод Маршрут Назначение
GET /api/payment-orders/sfr-recipients Отделения с проверенным справочником на текущий день компании; сохранённый выбор и ручные реквизиты
PUT /api/payment-orders/sfr-recipient Сохранить `{branchId: string
POST /api/payment-orders/preview Подбор непогашенных обязательств: `{period, channel: "ens"
POST /api/payment-orders Создать проверенное поручение: те же поля и sourceHash
GET /api/payment-orders/:id/export Экспорт поручения в формате 1С 1.03, windows-1251

Период — месяц ГГГГ-ММ, квартал ГГГГ-Qn или год. ЕНП учитывает оплату, ранний зачёт и остаток ЕНС; травматизм исключён из ЕНП и готовится отдельным каналом injury. Для СФР нужны проверенные реквизиты регионального отделения (payee): получатель, ИНН, КПП, счёт, банк, БИК, корреспондентский счёт и ОКТМО. Для текущих взносов на травматизм формируются КБК 79710212000061000160, статус плательщика 08 и КПП плательщика из настроек (у ИП — 0); поля 106–109 — 0. Изменение обязательств или реквизитов после проверки блокирует создание/экспорт. После повторного превью rebuild: true в POST создаёт новую ревизию по оставшейся сумме, старый экспорт блокируется; расчёт учитывает только активные версии документов. Выгрузка не означает оплату; факт оплаты — банковская операция и привязка к обязательству. Ответ справочника содержит on, selectedBranchId, manualPreference и recipients с branchId, branchName, status (ready, no_data, stale, ambiguous) и recipient. Массив включает и отделения без проверенной записи (no_data), sourceUrl ведёт на официальный ресурс отделения. Ручной manualPreference содержит payee, sourceUrl и checkedOn; ссылка может быть только HTTP(S), дата сверки не может быть позже текущего дня компании. Для injury выбранный sfrBranchId разрешается сервером по официальному версионированному справочнику на дату документа; переданный вместе с ним payee не используется. Недоступная, устаревшая или неоднозначная версия блокирует подготовку. КБК подтверждён только для 2026 года: в другие годы подготовка СФР блокируется и при ручном вводе. Ручной payee остаётся доступен при отсутствии sfrBranchId и отмечается recipientProvenance.status = manual; для проверенной записи статус verified и сохраняются её источник и даты. Выбор и ручные реквизиты изолированы по компании и меняются только с правами изменения налогов и money.pay. При экспорте проверяются сохранённая дата поручения и актуальная на момент экспорта версия справочника; устаревшая или изменившаяся версия блокирует экспорт.

Необязательный bankAccountId в обоих POST выбирает активный счёт компании. Номера поручений сохраняются при создании и повторном экспорте; общий атомарный счётчик по компании, счёту и дате предотвращает пересечение зарплатных, ЕНП и СФР-поручений.

Зарплата — /api/payroll

Метод Путь Доступ
GET /api/payroll/summary JWT; раздел «Сотрудники» — просмотр

Без права «Зарплаты и ПДн сотрудников» — только итоги месяца (total*, тариф, пояснения): employees: [] и employeesHidden: true. Итоги те же, что у бухгалтера (решение владельца: ФОТ без права — общей суммой).

Штатное расписание — /api/positions

Должности: название, оклад за полную ставку (salaryKopecks), число штатных ставок (staffRateMilli, в тысячных: 2500 — две с половиной) и признак руководителя организации (isHead). В ответе — сколько ставок уже занято работающими (occupiedRateMilli) и сколько на должности человек. Без права «Зарплаты и ПДн сотрудников» поля salaryKopecks в ответе нет (оклад должности — оклад каждого на ней), а PATCH с salaryKopecks — 403 с кодом access_denied.

Метод Путь Доступ
GET /api/positions JWT; раздел «Сотрудники» — просмотр
POST /api/positions JWT; раздел «Сотрудники» — изменение, право «Зарплаты и ПДн сотрудников»
PATCH /api/positions/:id JWT; раздел «Сотрудники» — изменение
DELETE /api/positions/:id JWT; раздел «Сотрудники» — управление
POST /api/positions/:id/archive JWT; раздел «Сотрудники» — изменение

Сотрудник занимает должность (positionId) на ставку rateMilli (1000 — полная, 500 — половина). При salaryFromPosition: true оклад считается сам: оклад должности × ставка, до копейки, и пересчитывается при правке оклада должности. Иначе оклад задаётся вручную. employmentBasis: labor_contract — трудовой договор, no_contract — руководитель — единственный участник без трудового договора; такое основание допустимо только на должности руководителя, при переводе на другую должность оно сбрасывается. Должность с сотрудниками не удаляется (409, archivable).

Реквизиты по ИНН — /api/requisites

Реквизиты организации или ИП из ЕГРЮЛ/ЕГРИП через DaData — для кнопки «Заполнить по ИНН». Ничего не сохраняет.

Метод Путь Доступ
GET /api/requisites/by-inn JWT; раздел «Контрагенты» — изменение

Параметр inn — 10 или 12 цифр. Ответ: kind (legal, entrepreneur), name, fullName, kpp, ogrn, address, oktmo, ifnsCode (у организации — первые четыре цифры КПП, иначе инспекция по адресу), directorName, okved, status («действует», «ликвидируется»…). Неверный ИНН — 400, не найден — 404, сервис не ответил — 502, ключ DADATA_API_KEY не задан или не принят — 503.

Единый налоговый счёт — /api/ens

Сальдо ЕНС и погашение налогов к уплате. Платёж с видом «Единый налоговый платёж» (код ens, распознаётся по «ЕНП», «единый налоговый платёж» или КБК 18201061201010000510) пополняет счёт; в срок уплаты обязательство из закрытого периода списывается с остатка, непокрытое — долг. Деньги дня раскладываются по п. 8 ст. 45 НК РФ: недоимка по НДФЛ, НДФЛ со сроком в этот день, прочая недоимка от ранней к поздней, прочие налоги срока; на налоги одного срока и одной ступени денег мало — пропорционально (п. 10 ст. 45); пени и штрафы не считаются. По уведомлению (считается поданным в последний день срока подачи, пока не отмечена фактическая подача) переплата зачитывается заранее в счёт НДФЛ и взносов на следующий рабочий день (п. 7, 9 ст. 78 НК РФ, абз. 1; уведомление на несколько месяцев по абз. 2 не моделируется) и возвращается, если возник долг. Годы, закрытые до этой очереди, и сданные до неё кварталы считаются прежней (docs/research/ens-order-corpus.md). Ручная привязка применяется раньше: так учитываются платежи не через ЕНП, например взносы на травматизм.

Метод Путь Доступ
GET /api/ens JWT; раздел «Налоги к уплате» — просмотр
POST /api/ens/allocations JWT; раздел «Налоги к уплате» — изменение, право «Готовить платежи»
DELETE /api/ens/allocations/:id JWT; раздел «Налоги к уплате» — изменение, право «Готовить платежи»
GET /api/ens/notice JWT; раздел «Налоги к уплате» — просмотр
GET /api/ens/notice/file JWT; раздел «Налоги к уплате» — просмотр, право «Готовить платежи»
GET /api/ens/payment JWT; раздел «Налоги к уплате» — просмотр
GET /api/ens/payment/file JWT; раздел «Налоги к уплате» — просмотр, право «Готовить платежи»
PUT /api/ens/obligations/:id/notice-filed JWT; раздел «Налоги к уплате» — изменение
DELETE /api/ens/obligations/:id/notice-filed JWT; раздел «Налоги к уплате» — изменение
GET /api/ens/balances JWT; раздел «Налоги к уплате» — просмотр
POST /api/ens/balances JWT; раздел «Налоги к уплате» — изменение, право «Готовить платежи»

GET — balanceKopecks (больше нуля — переплата, меньше — долг; без зачтённого заранее, как в личном кабинете), overdueKopecks, upcomingKopecks, reservedKopecks (зачтено заранее по уведомлениям и ждёт срока), obligations (обязательство, paidKopecks, reservedKopecks, reservedOn — день ожидаемого зачёта по уведомлению, status: paid, partial, overdue, covered — покрыто переплатой, upcoming; allocations — каким платежом и когда погашено; manual — ручные привязки) и payments (сколько каждого платежа ушло на налоги). POST /allocations — { transactionId, obligationId, amountKopecks }; сумма не больше остатка платежа и налога за вычетом других ручных привязок (400), повтор той же пары заменяет сумму. В obligation — отметка noticeFiledOn (ГГГГ-ММ-ДД) и noticeFiledByName.

Отметка «уведомление подано». PUT /obligations/:id/notice-filed — { on? } (дата подачи, по умолчанию сегодня в поясе компании; будущая или несуществующая — 400 с fields.on; налог без уведомления — 400 без полей); DELETE — снять. Отвечают тем же, что GET /api/ens. Подано позже срока подачи — ранний зачёт переносится на следующий рабочий день после отмеченной даты (п. 7 ст. 78 НК), раньше срока — считается по сроку, как без отметки. Налог без уведомления (НДС, УСН за IV квартал, взносы ИП 1%) — 400; срок уплаты в закрытом году или сданном налоговом квартале — 409: расчёт за них закреплён. Только люди (JWT). Период открыли заново — обязательство снимается вместе с отметкой. Период, по которому что-то уже погашено, заново не открыть (409 на /api/periods/reopen). Взносы, погашенные с ЕНС, в оценке налога уменьшают УСН «Доходы» и входят в расходы на «Доходах минус расходы».

Сверка с налоговой: POST /balances — { date (ГГГГ-ММ-ДД), balanceKopecks (плюс — переплата, минус — долг, как в личном кабинете), note? }, сальдо ЕНС по данным налоговой на дату; на ту же дату — правка, будущая дата — 400. Ответ и GET /balances (последние 24, свежие сверху) — с расчётом «Потока» на ту же дату: computedKopecks и differenceKopecks (налоговая минус расчёт). Последняя сверка — пункт ens-balance чек-листа закрытия квартала: не блокирует; расхождение — «Проверьте», сальдо не вносили — «не проверялось».

Уведомление об исчисленных суммах (КНД 1110355, формат 5.03) — за месяц уплаты ?month=ГГГГ-ММ: строки по НДФЛ, взносам и авансам УСН со сроком в этом месяце (у НДС и налога на прибыль декларация раньше срока). НДФЛ разложен по КБК ставок шкалы по расчёту сотрудников из закрытого месяца; период НДФЛ — месяц удержания (месяц уплаты, номер 01–03), взносов — месяц начисления, авансов УСН — 34/01–03. КБК — из налоговых норм (kbk.codes). GET /notice — состав, срок подачи (deadline, 25 число с переносом) и warnings: без ИНН, КПП, ОКТМО, кода инспекции или руководителя файл не выдаётся (409). GET /notice/file — XML в windows-1251 с именем UT_UVISCHSUMNAL_…; месяц без сроков — 404.

Платёж на ЕНП. GET /payment (необязательный bankAccountId) — сколько заплатить сейчас: налоги ближайшего срока плюс долг минус то, что уже лежит на счёте (amountKopecks, nearestDueDate, overdueKopecks, spareKopecks), получатель и КБК из норм ens.payee и kbk.codes, строка QR-кода по ГОСТ Р 56042-2014 (qrPayload) и warnings. GET /payment/file (amount в копейках, number, bankAccountId) — платёжное поручение в формате 1С «Клиент банка» 1.03 (1c_to_kl.txt, windows-1251) с реквизитами ЕНП по приказу Минфина № 58н: статус 01, КПП плательщика 0, ОКТМО и основание 0, назначение «ЕНП». Без ИНН или банковского счёта — 409.

Закрытие периодов — /api/periods

Пока период открыт, налоги считаются на лету — по нормам и условиям на его даты. Закрытие сохраняет расчёт целиком вместе с нормами (rulesUsed), по которым он сделан: новая версия нормы, правка оклада или режима закрытый период не меняют. GET /api/payroll/summary и GET /api/reports/tax-estimate для закрытого периода отдают сохранённый расчёт с отметкой closed (кто и когда закрыл), для открытого — closed: null.

Метод Путь Доступ
GET /api/periods JWT; раздел «Налоги к уплате» — просмотр
GET /api/periods/checklist JWT; раздел «Налоги к уплате» — просмотр
GET /api/periods/late JWT; раздел «Налоги к уплате» — просмотр
GET /api/periods/adjustments JWT; раздел «Налоги к уплате» — просмотр
GET /api/periods/adjustments/preview JWT; раздел «Налоги к уплате» — просмотр
POST /api/periods/adjustments JWT; раздел «Налоги к уплате» — изменение, право «Закрывать период»
POST /api/periods/adjustments/:id/complete JWT; раздел «Налоги к уплате» — изменение, право «Закрывать период»
POST /api/periods/correction JWT; раздел «Налоги к уплате» — изменение, право «Закрывать период»
GET /api/periods/filed JWT; раздел «Налоги к уплате» — просмотр
POST /api/periods/filed JWT; раздел «Налоги к уплате» — изменение, право «Сдавать отчётность»
POST /api/periods/unfile JWT; раздел «Налоги к уплате» — изменение, право «Сдавать отчётность»
POST /api/periods/close JWT; раздел «Налоги к уплате» — изменение, право «Закрывать период»
POST /api/periods/reopen JWT; раздел «Налоги к уплате» — изменение, право «Закрывать период»

Тело — { scope, period }: scope — payroll (месяц зарплаты, period — ГГГГ-ММ) или tax (квартал налога, ГГГГ-Qn). Закрытие даёт обязательства к уплате (obligations: taxKind, dueDate, amountKopecks): НДФЛ и взносы за месяц — до 28 числа следующего месяца; УСН за I–III квартал — до 28 числа месяца после квартала, за год — до 28 марта (ИП — 28 апреля); НДС — тремя равными долями до 28 числа трёх месяцев после квартала; налог на прибыль — как УСН у организации. Срок, выпавший на выходной или нерабочий праздничный день, переносится на следующий рабочий день по производственному календарю (ст. 112 ТК РФ и постановления о переносе выходных, п. 7 ст. 6.1 НК РФ). Второе закрытие — 409, период, который ещё не начался, — 400. reopen с reason (не короче 5 символов) открывает период заново: обязательства снимаются, запись о закрытии остаётся с отметкой, кто и почему открыл. GET /api/periods?year= — действующие закрытия года.

Чек-лист квартала. GET /api/periods/checklist?period=2026-Q3 — пункты проверки перед закрытием. У каждого: key, title, status (ok | warn | fail), blocking (мешает ли закрытию, когда fail), details (что не так, по-русски) и link ({label, path} — экран для исправления, либо null). Проверяются: квартал закончился; выписки по всем счетам загружены после конца квартала; все платежи разнесены по видам; поступления привязаны к счетам и «прочие» поступления; оплаченные счета закрыты документами; нет черновиков документов; у расходов есть документы («Доходы минус расходы»); месяцы зарплаты закрыты; заполнены ОКТМО и ИФНС; порог НДС на УСН; предыдущий квартал года закрыт; нет просроченных налогов на ЕНС. Пункт «остаток сходится с выпиской» сверяет сальдо 51 по журналу с остатком банка на конец квартала (checkBankBalances): расхождение или отсутствие остатка — warn, закрытие не блокирует. Для плательщика НДС незакрытые счета и черновики документов блокируют, на УСН без НДС — предупреждение. Ответ: items, canClose, confirmRequired (ОСНО), closed, late. POST /api/periods/close для налога отвечает 409 с code: "checklist_failed" и списком blockers, если есть блокирующий fail. На ОСНО тело закрытия несёт confirm: true — подтверждение, что суммы сверены (оценка ОСНО — грубый ориентир); без него 409 с code: "confirm_required". Чек-лист на момент закрытия сохраняется в записи о закрытии.

Запрет правок в закрытом периоде. Правка, меняющая расчёт закрытого периода, отвечает 409 с code: "period_closed", scope, period и текстом со ссылкой «откройте период заново»: платёж — смена вида, taxable, deductible, grossAmountKopecks (присланное без изменений не считается; не блокируются комментарии, категория, повторяемость, договор, а также платёж, пришедший после закрытия); POST /api/sales-documents/:id/issue и /cancel с датой закрытого квартала — только у плательщика НДС (ОСНО, УСН с НДС), на УСН без НДС выставление отвечает warning; POST /api/tax-profile с effectiveFrom не позже конца закрытого периода налога или зарплаты; сотрудники — премия и даты приёма/увольнения в закрытом месяце, оклад и ставка при закрытом текущем месяце. POST /api/transactions/recategorize закрытые кварталы пропускает.

Операции после закрытия. Импорт выписки не блокируется: операция с датой в закрытом квартале сохраняется с afterCloseOf (ключ квартала, поле есть в TransactionDto). GET /api/periods/late?year= — по закрытым кварталам года число таких операций и суммы поступлений и списаний (count, incomeKopecks, expenseKopecks) — баннер на странице «Налоги». Закрытие квартала заново снимает пометки.

Годовое закрытие УСН. Закрытие IV квартала на УСН — расчёт за год, а не аванс. Налог за год (минимальный налог 1% на «Доходах минус расходы» — только здесь, п. 6 ст. 346.18 НК РФ) минус авансы I–III кварталов с учётом внесённых корректировок (в периоде ошибки) даёт usn-обязательство на доплату: срок 28 марта (ООО) или 28 апреля (ИП) с переносом; если авансы больше налога, обязательства нет, разница — «к уменьшению». Для ИП добавляется обязательство contributions с periodKey: "ГГГГ" — 1% с дохода свыше 300 000 ₽ до 1 июля следующего года (уведомление по нему не подаётся). Итоги записываются в закрытие как annual (year, yearTaxKopecks, regularTaxKopecks, minimumTaxKopecks, minimumTaxApplied, advancesKopecks, advanceAdjustmentsKopecks, dueKopecks — плюс доплата, минус уменьшение, ipContribution, links на /api/reports/kudir и /api/reports/usn-declaration) и видны в GET /api/periods и в state.annual чек-листа; до закрытия — в annualPreview. Снимок IV квартала несёт ту же доплату (usnTaxKopecks, ytd.priorAdvancesKopecks, ytd.dueKopecks).

Взносы ИП за себя. Фиксированные взносы (ip.fixedContributions, ст. 430 НК РФ) выпускаются обязательством contributions с part: "ip_fixed", periodKey: "ГГГГ" и сроком 28 декабря (п. 2 ст. 432) при первом закрытии квартала года — не в конце года, чтобы «Налоги к уплате» и календарь показывали взнос заранее, а платить его можно было частями. Один раз за год; у ООО их нет. Сумма — за полный год (при регистрации ИП в течение года она пропорциональна). 1% с дохода сверх 300 000 ₽ — part: "ip_additional", при годовом закрытии. Уменьшение налога на эти взносы считает оценка налога, не обязательство.

Корректировки вместо переоткрытия. reopen работает, только пока по периоду нет оплаты, отметки «сдано» и корректировок; иначе 409 с code: "reopen_blocked" и correctionLink (для квартала налога). Исправление закрытого квартала вносится корректировкой (п. 1 ст. 54 НК РФ: ошибку исправляют в периоде ошибки, в текущем — только если она привела к переплате). Поток: (1) операция пришла задним числом (afterCloseOf) — её можно классифицировать сразу; правка уже учтённой операции сначала требует POST /api/periods/correction {period, reason} — «начать корректировку»: платежи и условия квартала можно править, закрытый снимок не меняется; (2) GET /api/periods/adjustments/preview?period=2026-Q1 — закрытие против данных сейчас: lines (taxKind, baselineKopecks, currentKopecks, deltaKopecks), direction (underpaid | overpaid | none), allowedRecognizedIn, nextStatus, warnings (пени, нужный отчёт); (3) POST /api/periods/adjustments {period, reason, recognizedIn?} — внести; разница пересчитывается на сервере. Недоплата становится обязательством с прежним сроком уплаты (adjustmentId в obligations), переплата в периоде ошибки уменьшает обязательства, в текущем периоде (recognizedIn: "current_period", только переплата) — обязательства не трогаются. Статус: needs_amended_notice (авансы УСН), needs_amended_declaration (год УСН, НДС, прибыль) или done; POST /api/periods/adjustments/:id/complete — уточнённый отчёт подан. Внесение снимает пометки afterCloseOf и завершает идущую корректировку. Уведомление за месяц срока (/api/ens/notice) несёт исправленную сумму одной строкой; порядок подачи уточнённого уведомления не подтверждён. 409 от запрета правок (period_closed) несёт correctionLink.

Сдано. POST /api/periods/filed {scope, period, on, note?} — отметить, что отчётность за закрытый период подана (on — ГГГГ-ММ-ДД, не в будущем и не раньше начала периода); POST /api/periods/unfile снимает. GET /api/periods/filed — все отметки (для календаря сроков: срок отчёта за сданный период показывается как «сдано»). Отметка есть в GET /api/periods (filed), в state чек-листа квартала. Квитанция или протокол — обычный документ библиотеки со связью {type: "period", id: <id закрытия>}.

Налоговый профиль — /api/tax-profile

Налоговые условия компании по периодам: режим (taxRegime), региональная ставка УСН (usnRatePercent, null — общая из налоговых норм), IT-льгота (isItCompany), реестр российского ПО (hasRegistrySoftware), пониженный тариф взносов МСП (mspReducedTariff), дни выплаты зарплаты (paySchedule) — каждое с даты effectiveFrom. Оценка налога берёт условия на начало квартала, расчёт зарплаты — на месяц: смена режима с января не пересчитывает прошлый год.

Метод Путь Доступ
GET /api/tax-profile JWT; раздел «Налоги к уплате» — просмотр
POST /api/tax-profile JWT; раздел «Налоги к уплате» — изменение
DELETE /api/tax-profile/:id JWT; раздел «Налоги к уплате» — управление
GET /api/tax-profile/pay-schedule JWT; раздел «Налоги к уплате» — просмотр
PUT /api/tax-profile/pay-schedule JWT; раздел «Налоги к уплате» — изменение

GET — current (условия на сегодня) и periods. Пока истории нет, период один — исходный, из настроек (id: null, с 2000-01-01). POST заводит период с даты или правит период на ту же дату; первая запись сначала сохраняет исходные условия, чтобы прошлое не изменилось. Региональная ставка — больше нуля и не выше общей на ту дату (400). Удалить можно только период, который ещё не начался (409). PATCH /api/settings с режимом или льготами при наличии истории заводит период с сегодняшнего дня; без истории — уточняет исходные условия.

Дни выплаты зарплаты (paySchedule, ТК РФ ст. 136). Для каждой выплаты: title, day (1–31, в коротком месяце — последний день), nextMonth (окончательный расчёт платится в следующем за периодом месяце) и период месяца начисления periodFrom–periodTo (1–31, 31 = конец месяца). Устаревший флаг withholdNdflFromAdvance сохранён для чтения старых данных: НДФЛ удерживается при каждой выплате независимо от него. GET /pay-schedule отдаёт {effectiveFrom, schedule, pending, warnings}: pending — уже записанный график с будущей датой ({effectiveFrom, schedule}), текущий месяц догуливает по прежним дням. PUT /pay-schedule записывает график с новой даты — создаёт период условий (по умолчанию с начала следующего месяца; первая настройка действует сразу; телом можно передать effectiveFrom — ГГГГ-ММ-ДД) и отвечает {effectiveFrom, schedule, warnings, closedMonths}. Прошлые месяцы не переписываются; будущие периоды получают тот же график, пока по ним ничего не закрыто; closedMonths — закрытые месяцы зарплаты с даты смены, которые останутся со своим снимком расчёта. warnings — не запреты: периоды не покрывают месяц, выплата позже 15 календарных дней после периода, отсчёт — от календарного конца периода (ч. 6 ст. 136 ТК), между выплатами больше полумесяца. По графику НДФЛ месяца раскладывается по датам выплаты: удержание 1–22 числа платится 28-го этого месяца, 23 – конец — 5-го следующего, 23–31 декабря — в последний рабочий день года (п. 6 ст. 226 НК). Дата дохода — день каждой выплаты, включая аванс (подп. 1 п. 1 ст. 223 НК, действует с 2023 года). Отключить удержание старым флагом withholdNdflFromAdvance нельзя. У обязательства появляются band и withheldIn (месяц удержания — код периода в уведомлении; срок подачи — 25-е месяца удержания для полосы 1–22 и 3-е следующего месяца для полосы 23–конец, п. 9 ст. 58 НК). Без графика — как раньше: одной суммой к 28-му следующего месяца; такая оценка не подтверждает удержанный налог для сдачи. PayrollSummary.payouts хранит отдельные выплаты каждого сотрудника: доход, НДФЛ и сумму на руки с разбивкой по ставкам. Налог рассчитывается последовательно по выплатам, не пропорциональным делением налога компании. Банковская метка payrollMonth связывает операцию с месяцем начисления, а подтверждение строки ведомости создаёт PayrollPayoutRecord — проверенный источник для уведомлений и 6-НДФЛ. Дата операции берётся календарным днём в часовом поясе компании. Календарь сроков (/api/reports/tax-deadlines) собирает сроки НДФЛ по графику каждого месяца (profile.payScheduleByMonth, 12 значений с января): смена дней выплаты не перерисовывает прошедшие месяцы. Кроме того, профиль несёт payoutFactsByMonth и headOnStaffByMonth (12 значений | null): были ли выплаты физлицам и числился ли руководитель по месяцам. По ним сроки получают relevance: suppressed с suppressReason — когда по фактам периода срок не возникает (нет выплат — нет НДФЛ; нет дохода — нет аванса УСН; нет НДС — нет сроков НДС); взносы (contributions-payment, rsv) при этом остаются реальными, если руководитель числится, — взносы с федерального МРОТ платят и без выплат (с 2026). Взносы на травматизм (efs1-injury, efs1-stazh, injury-payment) — наоборот: минимальная база (МРОТ) на 125-ФЗ не распространяется, без выплат физлицам их нет. У каждого срока есть periodKey (ГГГГ-Qn или ГГГГ-ММ) — тот же формат, что у требований к отчётности. Квартальные сроки подавляются, только когда закрыты ВСЕ три месяца квартала: закрытый пустой месяц не должен прятать реальные сроки за ещё не закрытыми; помесячные сроки (НДФЛ, взносы, персонифицированные) оцениваются по своему месяцу. Месяц без фактов (null — период не закрыт) не подавляется: без данных поведение прежнее. Подавленные сроки остаются в items (их видно по чекбоксу «показать неприменимые»), а коротким списком «почему срок не показан» они собраны в excluded: {ruleId, periodKey, suppressReason}. Напоминания письмом (за 5 дней до срока и в день срока) по подавленным срокам не приходят: письмо о сроке, которого на самом деле нет, — шум. Уведомления на экране «Налоги» собираются по месяцу подачи (у НДФЛ с полосой — от месяца удержания), и у каждой строки файла свой срок подачи noticeDue.

Факты деятельности и требования к отчётности — /api/reports/activity-facts, /api/reports/filing-requirements

Слой фактов (план автоматизации §3): «что реально было» вместо шаблона «у компании есть сотрудники».

GET /api/reports/activity-facts?year — факты по месяцам года: months (ключ ГГГГ-ММ) — начисления физлицам, фактические выплаты из выписки (hasPayouts, payoutCount), удержанный НДФЛ, руководитель на штате (headOnStaff) и взносы с его минимальной базы (headContributionsDueKopecks), доход УСН и НДС к уплате (по закрытым кварталам); aggregate — суммы года. Месяц без закрытия в months отсутствует. Удержанный НДФЛ — только из снимка закрытия месяца: обязательства (плановые суммы к уплате) фактом удержания не считаются.

GET /api/reports/filing-requirements?year&quarter&month — по каждой форме (artifact: ndfl_notice, ndfl6, rsv, personified, efs1, usn_notice, usn_payment, usn_declaration, vat_declaration, vat_payment, profit_declaration, profit_payment, contributions_payment, injury_payment; periodKey — ГГГГ-Qn или ГГГГ-ММ): required — существует ли обязанность по фактам периода, pending — осталось ли действие (не сдано), reason — почему, blockers — чего не хватает, sourceIds — источники. Панели «Отчётности» рендерятся по этому списку: неприменимые формы не показываются. ?quarter= и ?month= — ответ по одному периоду (квартал — его формы и месячные формы его месяцев; месяц — только формы этого месяца).

Формы режима: у компании на ОСНО форм УСН в ответе нет, и наоборот. Квартал оценивается, только когда закрыты все три его месяца; при неполных данных обязанность НЕ отрицается: форма остаётся required и pending с reason «Данных за период недостаточно: месяцы не закрыты». ЕФС-1 (травматизм, 125-ФЗ) — не РСВ: без выплат физлицам её не требуется (минимальная база МРОТ на 125-ФЗ не распространяется).

Годовые формы (usn_declaration, profit_declaration) — период в ответе String(year), а факт берётся за ВЕСЬ год, а не за IV квартал: сезонный бизнес с доходом в I–III кварталах и пустым IV кварталом декларацию всё равно подаёт. Год — с даты регистрации компании; если месяцы года неизвестны, обязанность не отрицается (reason «данных недостаточно»).

«Сдано» — отправка в статусе accepted, delivered, sent, created или prepared; needs_correction («нужна корректировка») и unknown сданными не считаются: по ним форма остаётся pending. Факт оплаты для payment-форм (usn_payment, vat_payment, contributions_payment, injury_payment) учитывается с волны W5 (PaymentOrder) — пока оплата не закрывает pending.

Превью форм (/personified, /rsv, /ndfl6, /efs1) отдают notRequiredReason: форма за период не сдаётся (нет выплат физлицам и удержанного НДФЛ) — спокойное пустое состояние вместо блокеров; у персонифицированных прежнее filingNotRequired (последний месяц квартала) сохранено и входит в notRequiredReason.

Автоматизация — /api/automation

Журнал конвейера автоматизации (план автоматизации §1, волна W1): список задач, которые конвейер собрался сделать и почему, с историей статусов и письмами по стадиям. Волна W1 — каркас: из реальных триггеров планировщик создаёт задачи-уведомления о ближайших налоговых сроках (≤ 3 дней), остальные виды (payroll_register, payment_order, filing) появятся в волнах W4–W6.

GET /api/automation/tasks?status&kind&year — список задач. Фильтры: status (scheduled, running, needs_review, draft, awaiting_signature, accepted, done, failed, cancelled, stale), kind (payroll_register, filing, payment_order, notification), year — по ключу периода (2026-10, 2026-Q1). В каждой задаче: naturalKey (ключ идемпотентности — одна активная задача на документ), periodKey, dueDate, reason (почему задача появилась или пропущена), sourceType/sourceIds (источник), status, attempts, lastError.

GET /api/automation/tasks/:id — карточка задачи: то же, что в списке, плюс history[] (переходы статусов: at, from, to, reason, by) и notifications[] (письма: стадия, получатель, статус, sentAt, attempts; sent ставится только после успешной отправки — сбой SMTP письмо не теряет).

POST /api/automation/tasks/:id/retry — ручной повтор (заглушка W1): снимает ошибку и ставит задачу обратно в очередь; выполняющийся под арендой — 409.

Метод Путь Доступ
GET /api/automation/tasks JWT; раздел «Налоги к уплате» — просмотр
GET /api/automation/tasks/:id JWT; раздел «Налоги к уплате» — просмотр
POST /api/automation/tasks/:id/retry JWT; раздел «Налоги к уплате» — редактирование

Фон конвейера выключен по умолчанию: env AUTOMATION_WORKER_ENABLED оператора плюс вложенные флаги Settings.automation компании (секция «Автоматизация» в настройках, все выключены по умолчанию). Выключение флага прекращает НОВЫЕ задачи и письма, но не отзывает уже созданные черновики.

Налоговые нормы — /api/tax-rules

Ставки, пороги и суммы, по которым считаются налоги и взносы: НДФЛ, МРОТ, предельная база, тарифы взносов, ставки УСН, минимальный налог, порог НДС на УСН, налог на прибыль, НДС. У каждой нормы — версии с датой вступления в силу и источником (статья НК РФ, закон, постановление); расчёт берёт версию на дату операции, а не «ставку года».

Метод Путь Доступ
GET /api/tax-rules JWT; раздел «Справочники» — просмотр

Ответ — по норме: code, label, unit (percent, kopecks, flag, bands, tariff), действующая сегодня current и вся история versions. Версии из кода — с id: null. Новую версию после выхода закона вносит оператор платформы: POST /api/platform/tax-rules с code, effectiveFrom (ГГГГ-ММ-ДД), value и source. Версию не правят; удалить (DELETE /api/platform/tax-rules/:id) можно только ещё не вступившую в силу — по действующей уже считали (409). Вторая версия на ту же дату — 409.

Проекты — /api/projects

Код проекта — две цифры внутри номера счёта (ГГППНННН). Список сразу отдаёт деньги по каждому проекту.

Метод Путь Доступ
GET /api/projects JWT; раздел «Проекты» — просмотр
POST /api/projects JWT; раздел «Проекты» — изменение
DELETE /api/projects/:id JWT; раздел «Проекты» — управление
PATCH /api/projects/:id JWT; раздел «Проекты» — изменение

Платформа — /api/platform

Раздел оператора платформы (isPlatformAdmin): компании, очередь на проверку и журнал фоновых задач (опрос выписки и сверка по компаниям — что сделано и какие ошибки). Выбор компании не нужен; внутрь компаний этот раздел не пускает — туда пускает только членство.

GET /tenants — все компании: статус, почта владельца (ownerEmail), число людей, когда попала в очередь (queuedAt) и последнее решение (reviewedAt, reviewedByName). ?status=pending — очередь на проверку: только компании, чей владелец подтвердил почту, дольше всех ждущие — первыми; другие значения status — просто фильтр.

POST /tenants/:id/approve — компания начинает работать, владельцу уходит письмо со ссылкой в кабинет; не подтвердившую почту одобрить нельзя (409). POST /tenants/:id/reject с { reason } — отказ, причина обязательна (её увидит владелец в письме и на экране статуса); ИНН отклонённой освобождается — в списке он виден, но зарегистрировать компанию с ним снова можно. Решение одно: по компании, которая уже не на проверке, — 409. Ответ обоих: { tenant, mailSent }. Каждое решение пишется в журнал — GET /tenants/:id/events: кто, когда, что и почему.

О каждой компании, попавшей в очередь, оператору уходит письмо на OPERATOR_EMAIL (если задан).

У каждой компании в списке — billing, сводка тарифа (как GET /api/billing). POST /tenants/:id/billing — ручная правка: { paidUntil?: "ГГГГ-ММ-ДД" | null, freeAccounts?, unlimited?, note }, причина обязательна и уходит в журнал. unlimited: true — все счета бесплатно (свои компании, партнёры); компания самого «Потока» (PLATFORM_TENANT_ID) без ограничений всегда. Продление, покрывающее выставленный неоплаченный период, закрывает долг.

Метод Путь Доступ
GET /api/platform/jobs JWT, оператор платформы
GET /api/platform/tax-rules JWT, оператор платформы
POST /api/platform/tax-rules JWT, оператор платформы
DELETE /api/platform/tax-rules/:id JWT, оператор платформы
GET /api/platform/tenants JWT, оператор платформы
POST /api/platform/tenants/:id/approve JWT, оператор платформы
POST /api/platform/tenants/:id/billing JWT, оператор платформы
GET /api/platform/tenants/:id/events JWT, оператор платформы
POST /api/platform/tenants/:id/reject JWT, оператор платформы

Акт сверки — /api/reconciliation

Сверка расчётов с контрагентом за период. Параметры — только в строке запроса, все три обязательны: counterpartyId, from и to (ГГГГ-ММ-ДД, включительно). Границы — календарные даты в часовом поясе компании (settings.timezone): платёж 31-го в 23:30 по Москве остаётся в этом месяце. Чужой или несуществующий контрагент — 404, неверные параметры — 400.

Метод Путь Доступ
GET /api/reconciliation JWT; раздел «Платежи» — просмотр
GET /api/reconciliation/pdf JWT; раздел «Платежи» — просмотр
GET /api/reconciliation/print JWT; раздел «Платежи» — просмотр

GET /api/reconciliation отдаёт расчёт: opening (сальдо на начало), lines (строки периода: date, kind, document, debitKopecks, creditKopecks), turnover (обороты), closing (сальдо на конец) и conclusion — итоговая фраза (text: «задолженность отсутствует» или «задолженность в пользу … сумма руб. (сумма прописью)») и inFavorOf (company — контрагент должен нам, counterparty — мы ему, null — расчёты сведены). Сальдо — пара debitKopecks/creditKopecks, заполнена одна сторона. notes — чего в расчёте нет: например, полученные документы без суммы. Контрагент без движений — нулевой акт.

Учёт ведётся по нашей стороне, как в 1С. Дебет — наши выставленные (не аннулированные, не черновики) документы продажи и исходящие платежи контрагенту с его ИНН. Кредит — входящие платежи: с его ИНН (в том числе ещё не разнесённые) и привязанные к его счетам; а также полученные от него закрывающие документы библиотеки (акт, накладная, УПД, чек) с указанной суммой. Сальдо на начало — все движения до from.

pdf — бланк файлом (Content-Disposition: attachment, имя вида akt-sverki-<from>_<to>.pdf), print — тот же бланк HTML-страницей для печати из браузера. Форма — по образцу 1С:БП, альбомная: слева «По данным» нашей компании, справа пустая половина для контрагента; факсимиле подписи и печать ставятся по флагу настроек для первичных документов.

Отчёты — /api/reports

Метод Путь Доступ
GET /api/reports/cash-flow-forecast JWT; раздел «Отчёты» — просмотр
GET /api/reports/counterparties JWT; раздел «Отчёты» — просмотр
GET /api/reports/pnl JWT; раздел «Отчёты» — просмотр
GET /api/reports/pnl/series JWT; раздел «Отчёты» — просмотр
GET /api/reports/tax-calendar JWT; раздел «Налоги к уплате» — просмотр
GET /api/reports/tax-deadlines JWT; раздел «Налоги к уплате» — просмотр
GET /api/reports/activity-facts JWT; раздел «Налоги к уплате» — просмотр
GET /api/reports/filing-requirements JWT; раздел «Налоги к уплате» — просмотр
GET /api/reports/contract-periods JWT; раздел «Отчёты» — просмотр
GET /api/reports/missing-docs JWT; раздел «Отчёты» — просмотр
GET /api/reports/unclosed-invoices JWT; раздел «Отчёты» — просмотр
GET /api/reports/tax-estimate JWT; раздел «Отчёты» — просмотр
GET /api/reports/kudir JWT; раздел «Налоги к уплате» — просмотр
GET /api/reports/kudir/pdf JWT; раздел «Налоги к уплате» — просмотр
GET /api/reports/kudir/xlsx JWT; раздел «Налоги к уплате» — просмотр, право «Массовые выгрузки»
GET /api/reports/usn-declaration JWT; раздел «Налоги к уплате» — просмотр
GET /api/reports/usn-declaration/pdf JWT; раздел «Налоги к уплате» — просмотр
GET /api/reports/usn-declaration/xml JWT; раздел «Налоги к уплате» — просмотр, право «Сдавать отчётность»
GET /api/reports/personified JWT; раздел «Налоги к уплате» — просмотр
GET /api/reports/personified/file JWT; раздел «Налоги к уплате» — просмотр, права «Сдавать отчётность» и «Зарплаты и ПДн сотрудников»
GET /api/reports/rsv JWT; раздел «Налоги к уплате» — просмотр
GET /api/reports/rsv/file JWT; раздел «Налоги к уплате» — просмотр, права «Сдавать отчётность» и «Зарплаты и ПДн сотрудников»
GET /api/reports/ndfl6 JWT; раздел «Налоги к уплате» — просмотр
GET /api/reports/ndfl6/file JWT; раздел «Налоги к уплате» — просмотр, право «Сдавать отчётность»
GET /api/reports/efs1 JWT; раздел «Налоги к уплате» — просмотр
GET /api/reports/efs1/file JWT; раздел «Налоги к уплате» — просмотр, право «Сдавать отчётность»

Без права «Зарплаты и ПДн сотрудников» ФОТ в P&L, прогнозе ДДС и оценке налога — общей суммой, как у всех (решение владельца), в counterparties выплаты сотрудникам сведены в строку «Сотрудники» без ИНН, в missing-docs у перевода сотруднику нет имени и назначения — те же правила, что в /api/transactions.

GET /api/reports/tax-estimate?year&quarter — оценка налога за квартал. Суммы incomeKopecks, expenseKopecks, contributionsKopecks — за квартал, из налогового регистра (тот же источник, что у КУДиР и декларации). Границы квартала — в часовом поясе компании. На УСН налог считается нарастающим итогом с 1 января: ytd — расчёт с начала года (taxKopecks — налог с начала года, priorAdvancesKopecks — авансы прошлых кварталов, из закрытых периодов или пересчётом, dueKopecks — разница, меньше нуля — переплата, annual — IV квартал, расчёт за год с минимальным налогом), taxBaseKopecks — база с начала года, usnTaxKopecks — УСН к уплате за квартал, taxKopecks — она же плюс НДС квартала. НДС на УСН (vatKopecks) — по первому событию (ст. 167 НК РФ): с отгрузки по выставленному документу продажи счёта или с аванса, с вычетом НДС с аванса при отгрузке и при возврате; поступление без счёта — как оплата с отгрузкой в тот же день; отрицательный НДС — к возмещению, в taxKopecks не входит. На ОСНО ytd: null, roughEstimate: true и closeNeedsConfirmation: true: оценка — грубый ориентир по оплатам, закрывать по ней квартал без сверки нельзя. «Возврат покупателю» (refund_out) уменьшает доход квартала возврата, «Возврат от поставщика» (refund_in) — расходы.

GET /api/reports/missing-docs — расходы, по которым не хватает закрывающего документа или основания. Параметры: from, to (ГГГГ-ММ-ДД), limit. В список не попадают виды платежей, у которых закрывающего документа не бывает (needsClosingDocs): налоги, взносы, зарплата, займы, дивиденды, переводы между своими счетами. Платёж без вида попадает — вид ещё не проставлен, а документ по нему, скорее всего, нужен. Расход считается закрытым актом, накладной, УПД или чеком; счёт на оплату расход не закрывает.

GET /api/reports/unclosed-invoices — обратная сторона того же контроля: оплаченные и частично оплаченные счета, по которым мы не выставили акт, накладную или УПД. Параметры: from, to (ГГГГ-ММ-ДД). Период — по дате последней оплаты в часовом поясе компании. Закрывают счёт только выставленные документы продажи (issued): черновик и аннулированный документ не в счёт. Остаток unclosedKopecks — оплачено минус закрыто документами, поэтому у частично оплаченного счёта закрывать нужно только оплаченную часть; счёт, закрытый на полную оплаченную сумму, в ответ не попадает. Ответ: items (invoiceId, number, counterpartyName, paidKopecks, closedKopecks, unclosedKopecks, paidOn, …), byCounterparty (count и amountKopecks по контрагенту, по убыванию суммы) и total — «не хватает N документов на X».

Сдача отчётности — /api/filings

Отправка сформированных форм через оператора (Saby) и журнал попыток. Единица — отправка (filing), а не период: у квартала бывает несколько отправок и корректировок (FilingSubmission, tenant-скоуп, коллекция filing_submissions). Реализованы проверка файла без сети и создание комплекта в Saby при включённых разрешениях. Подписывает и отправляет человек в Saby.

Метод Путь Доступ
POST /api/filings JWT; раздел «Налоги к уплате» — просмотр, право «Сдавать отчётность»; для personified/rsv ещё «Зарплаты и ПДн сотрудников»
GET /api/filings JWT; раздел «Налоги к уплате» — просмотр
GET /api/filings/:id JWT; раздел «Налоги к уплате» — просмотр

POST /api/filings — тело {kind, year?, quarter|month, dryRun?}, где kind — personified | rsv | ndfl6 | efs1 | tax-notice: месячные personified и tax-notice требуют month: ГГГГ-ММ, квартальные — year и quarter: 1…4. tax-notice — уведомление КНД 1110355, тот же файл, что в /api/ens/notice/file. dryRun: true собирает реальный файл теми же генераторами, что и выгрузка отчётов, и без сети возвращает FilingDryRunDto: реквизиты файла (fileName, knd, formatVersion, encoding, sizeBytes, sha256, containsPii), метаданные будущего комплекта (kit.type/subType/note — тип и подтип по справочнику Saby) и warnings. Без dryRun — реальная отправка: 201 {submission} — комплект создаётся в кабинете Saby (СБИС.ЗаписатьКомплект), подписывает и отправляет его руководитель в Saby (MVP «черновик + подпись в Saby», план §3.4). Замки до сети: отправка выключена в окружении (SABY_REPORTING_WRITE_ENABLED) — 409; у компании нет возможности reports_saby_write — 403; Saby не подключён — 409; за период уже есть активная отправка — 409; не заполнены реквизиты (код ИФНС, регномер СФР) — 409. Отказ Saby: не хватает тарифа — 402, слишком частые вызовы — 429, прочие сетевые — 502 (попытка в журнале помечается failed с текстом ошибки). Отправка — только для людей, ключ приложения — 403. Блокеры формы — 409 (незакрытые месяцы и т.п.), отказ такой же, как у /reports/*/file; для форм с ПДн без права «Зарплаты и ПДн сотрудников» — 403.

GET /api/filings?kind&status&year&quarter|month&page&limit — журнал отправок: {items, total}, где items — FilingSubmissionDto (статус с русской подписью, сырой код состояния оператора, события, sha256 файла, счётчик квитанций), total — всего отправок под фильтр. status фильтрует по статусу (prepared, created, sent, delivered, accepted, rejected, needs_correction, unknown, failed); page (с 1) и limit (по умолчанию 50, до 200) листают список, новые отправки сверху. GET /api/filings/:id — одна отправка; нет такой — 404. Файлы квитанций и статусная синхронизация появятся на этапе R2.

Документы продажи — /api/sales-documents

Акт, товарная накладная (ТОРГ-12) и УПД. Создаются из выставленного счёта: позиции и снимки сторон копируются, основание — договор счёта, а если его нет — сам счёт. У каждого вида своя сквозная нумерация: акт № 5 и накладная № 5 — норма, два акта № 5 — потерянный документ.

Метод Путь Доступ
GET /api/sales-documents JWT; раздел «Счета и продажи» — просмотр
POST /api/sales-documents JWT; раздел «Счета и продажи» — изменение
GET /api/sales-documents/:id JWT; раздел «Счета и продажи» — просмотр
PATCH /api/sales-documents/:id JWT; раздел «Счета и продажи» — изменение
DELETE /api/sales-documents/:id JWT; раздел «Счета и продажи» — управление
POST /api/sales-documents/:id/issue JWT; раздел «Счета и продажи» — изменение, право «Выставлять счета и документы»
POST /api/sales-documents/:id/cancel JWT; раздел «Счета и продажи» — управление, право «Выставлять счета и документы»
POST /api/sales-documents/:id/send-email JWT; раздел «Счета и продажи» — изменение, право «Отправлять письма»
PATCH /api/sales-documents/:id/exchange JWT; раздел «Счета и продажи» — изменение
POST /api/sales-documents/:id/document JWT; раздел «Счета и продажи» — изменение
GET /api/sales-documents/:id/print JWT; раздел «Счета и продажи» — просмотр
GET /api/sales-documents/:id/pdf JWT; раздел «Счета и продажи» — просмотр

POST /api/sales-documents принимает invoiceId, kind (act, waybill, upd), необязательные issuedOn (ГГГГ-ММ-ДД), comment и items — частичная отгрузка: [{ "index": 0, "quantityMilli": 4000 }], где index — номер позиции счёта с нуля. Закрыть больше, чем в счёте, по одной позиции нельзя (400); закрыть больше по сумме документов — можно, но ответ issue вернёт warning.

Фильтры списка: kind, status (draft, issued, cancelled), invoiceId, counterpartyId, from, to, q, awaiting=1 — те, что ещё ждут подписи.

У каждого документа есть отметка о подписи exchange: mode — edo (через ЭДО), paper (живая подпись) или none (подпись не нужна); sentAt — когда отправили, signedAt — когда подписан в ЭДО или вернулся подписанный экземпляр; url — ссылка на документ в ЭДО; note — чем отправляли бумагу. Способ подставляется от контрагента (exchange в его карточке): с кем работаем через ЭДО, тому документ и уходит в ЭДО. Меняется отдельным маршрутом PATCH /api/sales-documents/:id/exchange — он работает и у выставленного документа: содержание менять нельзя, а путь до подписи меняется каждый день. Отметка «подписан» без отправки проставляет и отправку; mode: "none" стирает обе даты.

Правится и удаляется только черновик: у выставленного есть номер, и он часть истории — такой документ аннулируют (cancel с обязательной причиной). Аннулирование номер в серию не возвращает.

Экземпляр в библиотеке. Выставленный документ получает экземпляр в библиотеке (documentId) — документ /api/documents с домом sales_document, связями с документом продажи, счётом и договором. В нём хранятся подписанный скан (POST /api/documents/:id/files) и место оригинала. Реквизиты идут в одну сторону: номер, дата, покупатель, сумма и НДС — из документа продажи; аннулирование ставит экземпляру метку «Аннулирован». Подпись остаётся у документа продажи (exchange), у экземпляра exchange.mode — none. Правка реквизитов, подписи, дома или замена связей у экземпляра через PATCH /api/documents/:id — 409, как и отвязка от своего документа продажи. PDF в экземпляре не хранится — его отдаёт GET /api/sales-documents/:id/pdf. POST /api/sales-documents/:id/document заводит экземпляр, если его нет (удалили или документ выставлен раньше, чем экземпляры появились), и возвращает его; у черновика — 409. Уже выставленным экземпляры заводит миграция при старте.

send-email отправляет выставленный документ покупателю письмом с PDF во вложении: тело { to, subject?, message? } — как у счёта, до 5 адресов, без темы и текста уходит письмо по шаблону («подписать и вернуть экземпляр»). Черновик и аннулированный — 409, кривой адрес — 400, почта не настроена — 503, почтовый сервер отказал — 502. Отправка пишется в журнал документа записью «отправлен письмом: адреса» и отметку exchange не меняет.

print отдаёт HTML для печати из браузера, pdf — тот же документ файлом (?download=1 — вложением). Формы: акт — свободная с обязательными реквизитами, ТОРГ-12 и УПД — по утверждённым формам, в альбомной ориентации. УПД с НДС — статус 1, он же счёт-фактура (/api/vat-invoices): строки 5 и 5б печатаются из зачёта авансов.

Чем закрыт счёт — GET /api/invoices/:id/documents: документы по счёту, closedKopecks (сумма выставленных) и uncoveredKopecks (остаток).

Счета-фактуры — /api/vat-invoices

Исходящие счета-фактуры плательщика НДС: отгрузочные (shipment), на аванс (advance) и корректировочные (correction, КСФ). Формы — ПП 1137 в ред. ПП № 26 (с 01.04.2026): прил. 1 и прил. 2. Разбор правил — docs/research/vat-ledgers.md.

Метод Путь Доступ
GET /api/vat-invoices JWT; раздел «Счета и продажи» — просмотр
GET /api/vat-invoices/settings JWT; раздел «Счета и продажи» — просмотр
PATCH /api/vat-invoices/settings JWT; раздел «Счета и продажи» — управление
POST /api/vat-invoices/advances/sync JWT; раздел «Счета и продажи» — изменение
POST /api/vat-invoices/corrections JWT; раздел «Счета и продажи» — изменение
GET /api/vat-invoices/:id JWT; раздел «Счета и продажи» — просмотр
PATCH /api/vat-invoices/:id JWT; раздел «Счета и продажи» — изменение
DELETE /api/vat-invoices/:id JWT; раздел «Счета и продажи» — управление
POST /api/vat-invoices/:id/issue JWT; раздел «Счета и продажи» — изменение, право «Выставлять счета и документы»
POST /api/vat-invoices/:id/annul JWT; раздел «Счета и продажи» — управление, право «Выставлять счета и документы»
POST /api/vat-invoices/:id/refunds JWT; раздел «Счета и продажи» — изменение, право «Выставлять счета и документы»
POST /api/vat-invoices/:id/revisions JWT; раздел «Счета и продажи» — изменение
GET /api/vat-invoices/:id/print JWT; раздел «Счета и продажи» — просмотр
GET /api/vat-invoices/:id/pdf JWT; раздел «Счета и продажи» — просмотр

Откуда берутся. Отгрузочный заводится сам, когда выставлен документ продажи с НДС (хоть одна позиция не «без НДС»). УПД статуса 1 — это и есть счёт-фактура: запись выставлена сразу, isUpd: true, номер и дата — УПД, позиции (lines) читаются из УПД, печать отдаёт бланк УПД. К акту и накладной — отдельный счёт-фактура с номером из серии счетов-фактур: при настройке primaryDocument: "separate" он выставляется вместе с документом, при "upd" (по умолчанию) остаётся черновиком со сроком dueOn. Аннулирование документа продажи аннулирует его счёт-фактуру (черновик — удаляет). Выставить или аннулировать счёт-фактуру по УПД отдельно нельзя — 409.

Авансы. На каждое поступление по счёту, пришедшее раньше отгрузки, заводится черновик счёта-фактуры на аванс (код 02): строки по ставкам счёта с расчётной ставкой (22/122, 10/110, 7/107, 5/105), в строке 5 (paymentDocs) — номер п/п из выписки и дата, dueOn — дата поступления + 5 дней (п. 3 ст. 168 НК). Позиции без НДС и 0% в аванс не входят; переплата сверх счёта — тоже. Отгрузки зачитывают авансы по порядку поступления (FIFO): зачёт пишется в отгрузочный счёт-фактуру — advanceLinks (строка 5б) с НДС в доле зачтённого и paymentDocs (строка 5). При skipSameQuarterAdvances: true (по умолчанию, МР ФНС п. 13) черновик на часть, зачтённую отгрузкой того же квартала, не нужен: полностью зачтённый снимается, частично — уменьшается. Выставленный не пересчитывается. У авансового в DTO advance: coveredKopecks, paymentKopecks, зачёты offsets, возвраты refunds, остаток remainingKopecks и remainingVatKopecks. Синхронизация идёт после выставления и аннулирования документов продажи, раз в час в фоне и по POST /api/vat-invoices/advances/sync (тело {} — счета с оплатами за полгода, { "invoiceId" } — один счёт; ответ { created, updated, removed }). Поступления без счёта (только по договору) пока не разбираются.

Номера. Выдаются при выставлении, черновик номер не тратит; неудачное сохранение номер возвращает — нумерация без дыр. Серии: vat_invoice — отгрузочные к актам и КСФ, vat_advance — авансовые, с префиксом А-; счётчик с начала года. Номер УПД статуса 1 — из серии УПД. Серии счетов-фактур в /api/invoice-series не показываются. Если в серии уже есть документ с более поздней датой и меньшим номером, issue вернёт warning о нарушенной хронологии.

Фильтры списка: kind, status (draft, issued, annulled), invoiceId, salesDocumentId, counterpartyId, quarter (ГГГГ-Qn), from, to, q, due=1 — черновики со сроком по возрастанию срока. Заменённые исправлением скрыты; all=1 — показать все.

PATCH /:id — только черновик: issuedOn (кроме отгрузочного и исправления), comment, paymentDocs: [{ number, date }] (авансовый: номер п/п, если банк его не прислал — без номера issue отвечает 400), counterparty: { name, inn, kpp, legalAddress } (только исправление). Удаляются (DELETE) только черновики КСФ и исправлений; черновики авансовых и отгрузочных снимает система — 409.

POST /corrections — КСФ к действующему выставленному отгрузочному: { vatInvoiceId, date, items: [{ index, quantityMilli, priceKopecks }], comment? }, index — строка исходного с нуля. В КСФ попадают изменённые строки (correctionLines: before — строка А, after — Б), итоги increase (В) и decrease (Г); operationCode — 01 при увеличении, 18 при уменьшении. Ничего не изменилось — 400.

POST /:id/revisions — исправление (строка 1а): { date }, черновик-копия с тем же номером и датой, revision: { number, date }, покупатель — из текущей карточки. При выставлении исходный получает revisedById. УПД так не исправляется — 409.

POST /:id/refunds — возврат аванса по выставленному авансовому: { date, amountKopecks, documentNumber?, transactionId? }, не больше остатка (400); НДС с возвращённой части — к вычету по коду 22. Авансовый с зачётом или возвратом не аннулируется — 409.

Настройки (/settings): primaryDocument — upd или separate; skipSameQuarterAdvances; advanceReminders — письмо владельцам за день до срока выставления и в день срока.

print — HTML, pdf — файл (?download=1 — вложением). Факсимиле в счёте-фактуре не ставится (п. 6 ст. 169 НК). Выставление и аннулирование в закрытом квартале — как у документов продажи.

Книги НДС — /api/vat-ledgers

Книга продаж и книга покупок за квартал по формам прил. 5 и 4 ПП 1137 в ред. ПП № 26 (vat-ledgers.md T-V5–T-V9, T-V12). Пока квартал открыт, книга собирается из документов на лету — «на конец квартала»: аннулирование или исправление после конца квартала в эту книгу не входит. Закрытый квартал (есть PeriodClose по scope=tax) отдаётся снимком на закрытие — хранимой книгой (stored); изменения документов закрытого квартала (сделанные во время корректировки) видны в дополнительном листе stored.supplement, а stored.needsAmendedDeclaration говорит, что по кварталу нужна уточнённая декларация (п. 1 ст. 81 НК).

Метод Путь Доступ
GET /api/vat-ledgers JWT; раздел «Налоги» — просмотр
GET /api/vat-ledgers/pdf JWT; раздел «Налоги» — просмотр, право «Выгружать данные»
GET /api/vat-ledgers/xlsx JWT; раздел «Налоги» — просмотр, право «Выгружать данные»
GET /api/vat-ledgers/declaration JWT; раздел «Налоги» — просмотр, право «Выгружать данные»

Квартал — в query: ?quarter=2026-Q3; без него или в другом виде — 400.

XML декларации (GET /api/vat-ledgers/declaration, формат 5.12 по приказу ЕД-7-3/989@ в ред. ЕД-7-3/1227@): какой файл — в query ?file=: NO_NDS (титульный лист, раздел 1 — к уплате, раздел 3 — из книг), NO_NDS_9 (книга продаж), NO_NDS_8 (книга покупок); файла с нулевой книгой в пакете нет. Для закрытого квартала с изменениями — ?correction=1: NO_NDS_91 и NO_NDS_81 (приложения 1 — дополнительные листы, НомКорр=1); без закрытия — 409, без такого файла в пакете — 404. Файл — в windows-1251, ИдФайл совпадает с именем файла без расширения; в Content-Disposition — имя по шаблону NO_NDS…_КодНО_КодНО_ИНН+КПП_ГГГГММДД_ЧЧММСС.xml.

Книга продаж (sales.rows, графы 1–19): 01 — выставленные отгрузочные счета-фактуры (УПД статуса 1 и к актам) с датой в квартале, гр. 11а (advanceInvoices) — зачтённые авансовые; 02 — авансовые квартала поступления оплаты: гр. 11 (paymentDocs) — платёжка, гр. 13б — сумма, НДС по расчётной ставке в гр. 17–18б, гр. 14–16 пустые; 01 с гр. 5 (correction) — КСФ на увеличение. Суммы по ставкам — base (гр. 14, 14а, 15, 15а, 15б, 16) и vat (гр. 17, 17а, 18, 18а, 18б) с ключами 22, 20, 10, 7, 5, 0; итоги — sales.totals, НДС всего — sales.vatKopecks. Позиции «без НДС» в книгу не регистрируются.

Книга покупок (purchases.rows, графы 1–15): 22 — вычет НДС с аванса при отгрузке (гр. 3 — авансовый, гр. 7а shipmentInvoices — отгрузочный, в квартале отгрузки) и при возврате аванса (гр. 7 paymentDocs — наша платёжка); 18 — наша КСФ на уменьшение (гр. 9–10 — наши данные); 01 — входящие УПД статуса 1 и счета-фактуры из реестра документов (purchaseInvoice) с кварталом вычета (заявленным или по дате принятия на учёт), для облагаемых операций. Входящие идут в книгу, только если на конец квартала компания принимает входной НДС к вычету — ОСНО или УСН с общей ставкой (inputVatDeductible); на УСН 5% и 7% — только коды 22 и 18. Итог гр. 15 — purchases.vatKopecks.

У строки source — откуда она: { kind: "vat_invoice" | "library_document", id }. payableKopecks — книга продаж минус книга покупок. warnings — что не попало и почему: невыставленные счета-фактуры квартала, зачёт по черновику аванса, входящие без номера или даты принятия, входящие на УСН 5/7. Также vatPayer, regimeLabel («ОСНО», «УСН; НДС 5% без вычетов»), seller, from, to.

pdf — книги по форме для бухгалтера и подписи руководителя: без book — обе одним файлом, book=sales или book=purchases — одна. xlsx — два листа с теми же графами.

Настройки компании — /api/settings

Реквизиты компании проверяются так же, как у контрагента; тип — по ИНН: 10 цифр — ООО, 12 — ИП (у ИП нет КПП). Ошибка — 400 с fields.

Для печатной формы счёта: phone и email печатаются в строке «Поставщик», invoiceTerms — текст условий под сроком оплаты (у каждой компании свой), directorName и accountantName — подписи.

paymentPollMinutes — как часто «Поток» сам забирает выписку и разносит оплаты: 5, 15, 30 или 60 минут, 0 — только вручную (по умолчанию 15).

sfrRegNumber — регистрационный номер в СФР (10 цифр, как в письме фонда о регистрации страхователя; пусто — снять): нужен для ЕФС-1 — без него форма не собирается, а отправка отчётности в Saby отвечает отказом. Для форм ФНС (6-НДФЛ, РСВ, персонифицированные) не используется.

Метод Путь Доступ
GET /api/settings JWT; любой член компании
PATCH /api/settings JWT; право «Настройки компании»

Платежи (выписка) — /api/transactions

Платежи приезжают из выписки банка и не создаются через API — только читаются и размечаются.

Поиск q ищет по назначению и контрагенту, а строка, похожая на сумму (52 300, 52300,50), — ещё и платежи ровно на эту сумму. Кусок числа вроде 52 суммой не считается.

Без права «Зарплаты и ПДн сотрудников» у выплат сотрудникам — вид платежа с ролью salary или получатель, совпавший по имени с сотрудником, — counterpartyName и purpose заменены на «Выплата сотруднику» («Поступление от сотрудника» для входящих), counterpartyInn — null, comments — пустой список (в тексте комментария бывает фамилия). Сумма, дата и вид остаются. Так в списке, карточке, ответах на комментарии и в export.csv. Текстом такие платежи не находятся (иначе поиск по фамилии выдал бы сумму), по сумме — находятся, скрытыми. Та же подпись — в акте сверки, в списке платежей договора, в неразнесённых поступлениях и в подписи связи документа с платежом.

Метод Путь Доступ
GET /api/transactions JWT; раздел «Платежи» — просмотр
GET /api/transactions/:id JWT; раздел «Платежи» — просмотр
PATCH /api/transactions/:id JWT; раздел «Платежи» — изменение
POST /api/transactions/:id/comments JWT; раздел «Платежи» — просмотр
DELETE /api/transactions/:id/comments/:commentId JWT; раздел «Платежи» — просмотр
GET /api/transactions/export.csv JWT; раздел «Платежи» — просмотр, право «Массовые выгрузки»
POST /api/transactions/recategorize JWT; раздел «Платежи» — изменение

PATCH принимает, кроме вида, категории и признаков учёта, payrollMonth («ГГГГ-ММ», null снимает пометку) — «за какой месяц» у выплаты зарплаты: только у платежей вида с ролью/кодом salary (400 у прочих). Дата такой операции заменяет плановую дату выплаты из графика при раскладке НДФЛ этого месяца; если месяц уже закрыт — раскладка не пересчитается, ответ несёт warnings со строкой об этом (пометка пригодится при открытии периода или корректировке). payrollCorrection (boolean) — помеченная «за месяц» операция не выплата, а корректировка (повторная отправка вернувшейся зарплаты, исправительная проводка): в раскладку НДФЛ не входит и плановую выплату не замещает. true без payrollMonth — 400; снятие payrollMonth снимает и признак. В DTO операции — поле payrollCorrection.

Вебхуки — /api/webhooks

Адреса, на которые «Поток» шлёт события компании: invoice.issued, invoice.paid, invoice.partially_paid, invoice.payment_removed, invoice.cancelled, payment.received, document.issued, document.cancelled, а также события конвейера автоматизации automation.task.ready (задача конвейера готова к действию человека) и automation.task.failed (задача конвейера упала). Настраивает администратор компании или ключ приложения со скоупом webhooks:write. Секрет подписи показывается один раз — при создании и смене (rotate-secret). test шлёт событие ping сразу и возвращает итог попытки; deliveries — последние 50 доставок с кодом ответа и временем повтора.

Каждое событие приходит POST-запросом с телом { id, type, createdAt, data } и заголовками X-Potok-Event (тип), X-Potok-Delivery (id доставки, при повторе тот же) и X-Potok-Signature (t=<unix-время>,v1=<HMAC-SHA256 секретом от «t.тело»>). В data у событий invoice.* — invoice (счёт целиком, как в GET /api/invoices/:id), у payment.received — payment (id, bankAccountId, date, amountKopecks, counterpartyName, counterpartyInn, purpose), у ping — message. Не 2xx за 10 секунд — повтор через 1, 5, 30 минут, 2, 6, 12 и 24 часа. Адрес — только https и не во внутреннюю сеть; переадресации не выполняются. Подробно, с проверкой подписи, — в руководстве по интеграции.

Метод Путь Доступ
GET /api/webhooks JWT с правом «Интеграции» или ключ со скоупом webhooks:write
POST /api/webhooks JWT с правом «Интеграции» или ключ со скоупом webhooks:write
PATCH /api/webhooks/:id JWT с правом «Интеграции» или ключ со скоупом webhooks:write
DELETE /api/webhooks/:id JWT с правом «Интеграции» или ключ со скоупом webhooks:write
POST /api/webhooks/:id/rotate-secret JWT с правом «Интеграции» или ключ со скоупом webhooks:write
POST /api/webhooks/:id/test JWT с правом «Интеграции» или ключ со скоупом webhooks:write
GET /api/webhooks/:id/deliveries JWT с правом «Интеграции» или ключ со скоупом webhooks:write

Интеграция Saby Docs — /api/saby

Подключение кабинета Saby (ЭДО Тензора) к компании: сервисным ключом приложения (рекомендуется, без SMS; выпускается в Saby в «Настройки → Безопасность → Подключения к Saby») или логином и паролем пользователя Saby — владельцы рекомендуют завести отдельного сотрудника «для Потока» без подтверждения входа по SMS. Если SMS у пользователя включено, подключение встаёт в статус needs_sms: Saby шлёт код, человек вводит его на маршруте sms-code (ОтправитьКодАутентификации/ПодтвердитьВход). Пароль, секрет ключа и сессии хранятся зашифрованными AES-256-GCM и в ответах API не возвращаются никогда; секреты способа входа, которым не пользуются, при подключении стираются. При подключении сразу выполняется вход: неверные данные подключение не сохраняют (400), нехватка тарифа «Обмен с контрагентами» — 402. Подключённая компания опрашивается фоном (saby-sync) по курсору СписокИзменений, при первом подключении — с начала текущего квартала; входящие документы заводятся в «Документах» черновиками (recognitionDraft) с полями из XML УПД 5.03 (XSD — nalog.gov.ru, schemas/), оригинальным XML и подписями вложения; исходящие (черновики, записанные из документов продаж) приходят той же синхронизацией и сопоставляются по exchange.externalId — отметки «отправлен» и «подписан контрагентом» ставятся сами. Режим «только чтение» включён по умолчанию для всех вызовов Saby. Интеграция входит во все тарифы; при выключении флага тарифа маршруты подключения и синхронизации отвечают 403. Маршруты изменения — только для людей: ключ приложения со скоупом webhooks:write интеграцию менять не может (403). connect и sms-code ограничены по частоте на компанию: 429 после 5 (соответственно 10) попыток за 10 минут.

Контур записи (срез S5). Запись в Saby — проверка контрагента (ИнформацияОКонтрагенте заводит его карточку, если его там нет), приглашение и черновик исходящего — закрыта двумя выключателями сразу: окружением SABY_WRITE_ENABLED (по умолчанию false) и возможностью компании edo_saby_write (по умолчанию выключена у всех, включается только явным списком возможностей; hasFeature её не выдаёт никогда — только явная проверка). Замок продублирован на уровне клиента: readOnly: false без внутреннего признака вызова из контура записи отклоняется до сети, и метод обязан быть из списка пишущих (ИнформацияОКонтрагенте, ЗаписатьДокумент, ОтправитьПриглашение). Пока запись выключена, маршруты записи не падают, а отвечают 200 { dryRun: true, request, hint } — что было бы отправлено, без секретов и без base64 вложения (только имя файла и размер). Реального запроса в Saby при этом нет ни одного. Состояние выключателей видно вебу в GET /api/saby как writeEnabled.

Контур записи отчётности (комплекты СБИС.ЗаписатьКомплект, раздел «Отчёты») — те же два замка со своими именами: окружение SABY_REPORTING_WRITE_ENABLED и возможность компании reports_saby_write. Админ компании включает её сам в этом же разделе — PATCH /api/saby/reporting тело { enabled } (обе возможности сразу: reports_saby и reports_saby_write); оба замка видны в статусе как reportingEnabled (компания) и reportingWriteEnv (окружение). Маршрут — только для людей с правом «Интеграции».

Статусы исходящих. Исходящие статусы приходят той же синхронизацией по exchange.externalId, время отметок — время события Saby, не синхронизации: 3/4 → exchange.sentAt, 7 → exchange.sentAt и exchange.signedAt, 10/23 → exchange.sentAt (документ ждёт подписи контрагента), 9 → exchange.rejectedAt «Отклонён контрагентом» с причиной в exchange.note (из фильтра «ждут подписи» документ уходит), 19/20/22 с говорящим названием — заметка об отзыве/аннулировании. Каждый документ обрабатывается один раз за запуск.

Метод Путь Доступ
GET /api/saby JWT; раздел «Документы» на просмотр. Статус подключения без секретов: available (включено тарифом), writeEnabled (запись в Saby реально открыта: окружение и возможность edo_saby_write компании — иначе интерфейс не обещает отправку), reportingEnabled (возможность компании reports_saby_write — отправка отчётности) и reportingWriteEnv (серверный SABY_REPORTING_WRITE_ENABLED; отправка работает, когда оба замка открыты), canManage (право «Интеграции» у текущего пользователя), connected (всё, кроме disconnected — при error кабинет подключён), stand, authMode (password/service_key), serviceKeyTail (хвост идентификатора ключа), login (только при canManage), status (connected/needs_sms/error/disconnected), phoneMask и codeExpiresAt при ожидании кода, ourOrg, syncFrom, pollMinutes, lastSyncAt, lastSyncError, lastSyncStats (created/updated/skipped/unknownTypes/failed)
PATCH /api/saby/reporting JWT с правом «Интеграции», только человек. Тело { enabled } — включает или выключает у компании отправку отчётности (reports_saby + reports_saby_write); ответ { reportingEnabled, reportingWriteEnv }. Серверный замок SABY_REPORTING_WRITE_ENABLED этим маршрутом не трогается — без него отправка из «Налогов» отвечает 409, даже когда возможность включена
POST /api/saby/connect JWT с правом «Интеграции». Тело { authMode?, login?, password?, serviceKeyId?, serviceKeySecret?, accountNumber?, stand?, syncFrom?, pollMinutes? } — либо пароль (логин обязателен), либо пара ключа (логин не нужен); ответ — статус, как GET. При двухфакторке — статус needs_sms с маской телефона; 429 — чаще 5 попыток за 10 минут с компании
POST /api/saby/sms-code JWT с правом «Интеграции». Тело { code } — подтверждение входа; 409, если код не запрашивался; 429 — чаще 10 попыток за 10 минут
POST /api/saby/sms-code/resend JWT с правом «Интеграции». Просить Saby отправить код ещё раз; 409, если идентификатор подтверждения истёк
POST /api/saby/sync JWT с правом «Интеграции». «Забрать сейчас»: не чаще раза в минуту (429); 409, если не подключено или не введён код из SMS
DELETE /api/saby JWT с правом «Интеграции». Отключить: выход из кабинета, стирание пароля и сессии; документы остаются. 404, если подключения нет
POST /api/saby/counterparties/:id/check Только JWT (человек); раздел «Контрагенты» на изменение. Проверка контрагента в ЭДО (ИнформацияОКонтрагенте с ДопПоля: "СписокИдентификаторов", чтобы Saby вернул ящики): ЮЛ — СвЮЛ с ИНН и КПП, ИП — СвФЛ по 12-значному ИНН. Тело { force? } — «Проверить ещё раз»; без него результат живёт на карточке сутки (cached: true), повторный вопрос Saby не зовёт. Ответ { edo: { checkedAt, state: saby|roaming|invited|none|refused|unknown, operator, participantId, boxes[], invitedAt }, cached }. Незнакомая форма ответа Saby — state: unknown с warning, кэшем не считается: повторная проверка идёт в Saby сразу. Побочный эффект Saby: нет карточки — заводится (потому маршрут и по кнопке, массово не звать). 400 — нет ИНН или физлицо; 409 — Saby не подключён; 429 — чаще 30 проверок за 10 минут с компании; при выключенной записи — { dryRun: true, request }
POST /api/saby/counterparties/:id/invite Только JWT (человек); раздел «Контрагенты» на изменение. Приглашение к ЭДО (ОтправитьПриглашение), тело { email }. Роуминговому контрагенту передаётся ИдентификаторАЯ — идентификатор ящика из последней проверки. Ответ { ok, invitationId, edo } — на карточке пометка «Приглашён» (state: invited, invitedAt). Приглашение без свежей проверки кэш проверки не подменяет; тому, кто уже «В Saby», приглашение не нужно — 409. Статус приглашения (СписокИзмененийПриглашений) пока не отслеживается. 400 — нет ИНН, физлицо или кривой адрес; при выключенной записи — { dryRun: true, request }
POST /api/saby/sales-documents/:id/send Только JWT (человек) с правом «Выставлять счета и документы». Черновик исходящего в Saby (ЗаписатьДокумент, регламент «Реализация») из PDF документа продаж: только выставленные акт/накладная/УПД со статусом 2. УПД со статусом 1 — 409 «передаётся только формализованным XML — появится позже»; уже подписанный (любой режим) или уже отправленный через ЭДО — 409, повтор по черновику без отметок об отправке разрешён (тот же идентификатор). Идентификатор черновика — стабильный uuid документа: повтор перезаписывает тот же черновик. Перед записью контрагент проверяется (кэш или вызов); не в ЭДО — не блокирует, в ответе warning. Ответ { document } с exchange: { mode: edo, url, externalId, system: saby, draftAt } — ссылка ведёт на подписание в Saby. 409 — черновик или аннулирован; 429 — чаще 20 отправок за 10 минут с компании; при выключенной записи — { dryRun: true, request } без base64 вложения

Пользователи — /api/users

Сотрудники компании запроса и их роли в ней. Обычный путь добавить сотрудника — приглашение (/api/invites); POST /api/users с временным паролем — запасной, когда почта недоступна. PATCH меняет роль только в этой компании; имя человека, который работает и в других компаниях, отсюда не меняется (409). DELETE убирает человека из компании; учётная запись удаляется, только если других компаний у него нет. Email, уже занятый на платформе, — 409.

Метод Путь Доступ
GET /api/users JWT; право «Управление пользователями»
POST /api/users JWT; право «Управление пользователями»
DELETE /api/users/:id JWT; право «Управление пользователями»
PATCH /api/users/:id JWT; право «Управление пользователями»