REST API

Apiin построен по принципу API-first: любое действие в кабинете — это вызов публичного REST API. Тот же контракт доступен внешним интеграциям и MCP-серверу для нейросетей.

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

Ключи создаются в кабинете, в разделе API и MCP, — программно ключ выпустить нельзя. Каждому ключу выдаётся набор прав (scopes) и срок действия (постоянный или до даты). Полный ключ имеет вид apiin_live_… и передаётся в заголовке:

Authorization: Bearer apiin_live_xxxxxxxx

Один ключ может работать с несколькими организациями (набор задаётся при создании). Активных ключей у организации может быть до 25 (отозванные не считаются), а если секрет скомпрометирован — ключ перевыпускается в кабинете: меняется только секрет, а имя, права, срок и набор организаций остаются, поэтому в интеграции достаточно подменить строку ключа. Базовый адрес — https://api.apiin.ru. Лимит — 600 запросов в минуту на ключ; при превышении приходит 429. Ключ можно отозвать в любой момент.

Права (scopes)

Действие требует соответствующего права; * — все права. Права строгие: documents:write НЕ включает удаление, отметки оплаты и подписи — они выдаются отдельно. Полный каталог с примерами наборов — на странице Права и роли.

  • documents:read / documents:write — документы, PDF, копии, связки, теги, нумерация;
  • documents:delete, documents:sign — удаление документа, отметка подписи закрывающего документа (акт, УПД, отчёт);
  • payments:write — отметки оплаты, платежи и правка даты оплаты, сверка выписки, ссылка на онлайн-оплату;
  • counterparties:read / counterparties:write / counterparties:delete;
  • orders:read / orders:write — заказы;
  • products:read / products:write / products:delete, storefront:write — каталог и витрина;
  • employees:use / employees:read / employees:write — сотрудники в доверенностях, справочник с паспортами, его правка;
  • messages:read / messages:write — раздел «Сообщения»: переписка, обсуждения, приглашение к переписке;
  • money:read / money:write — раздел «Деньги»: счета, операции, статьи, расходные документы и чеки, выписки (привязка денег к счетам — ещё и payments:write); акт сверки — money:read вместе с documents:read, начальный остаток контрагента — money:write;
  • public_links:write — публичные ссылки; send:write — отправка на email (и акта сверки, история его отправок — ещё с money:read);
  • templates:read / templates:write, assets:write, offers:write — оформление;
  • organizations:read / organizations:write — реквизиты и настройки;
  • webhooks:write — настройка вебхуков.

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

Организации

Все ресурсы скоупятся по организации: /api/organizations/{org_id}/…. Доступные ключу организации возвращает GET /api/whoami:

GET https://api.apiin.ru/api/whoami
Authorization: Bearer apiin_live_xxxxxxxx

→ { "organization_id": "<первая>", "organization_ids": ["<uuid>", …] }

Банковские счета и платёжный QR

Счета организации — GET/POST/PATCH/DELETE /api/organizations/{org_id}/bank-accounts (organizations:read / organizations:write). Платёжный QR (ГОСТ Р 56042-2014) печатается в счёте, когда у счёта по умолчанию заполнены расчётный счёт, БИК и корр. счёт; ИНН для QR не обязателен. В каждом счёте организации приходит готовность:

GET https://api.apiin.ru/api/organizations/{org_id}/bank-accounts

→ [ { "id": "…", "bank_name": "АО «АЛЬФА-БАНК»", "bik": "044525593",
      "account_number": "…", "corr_account": "…", "is_default": true,
      "payment_qr": { "ready": true, "missing": [], "personal_account": true } } ]

missing — коды незаполненных полей (account_number, bik, corr_account); personal_account — счёт физлица (40817/40820): такой QR принимают не все банки плательщиков. Сводка по организации — GET …/payment-qr: те же поля плюс source (bank_account | legacy | none) и bank_name.

Оплата для физлиц (самозанятые)

Самозанятый (тип self_employed или налоговый режим npd) может показать на публичной карточке реквизитов способы перевода для плательщиков-физлиц: телефоны для СБП с банками-получателями, карты и ссылку на оплату от банка (по ней рисуется QR). Настройки хранятся целиком и заменяются одним запросом; сервер приводит телефон к +79XXXXXXXXX, карту — к цифрам с проверкой по алгоритму Луна, ссылку принимает только https://. Лимиты: 5 телефонов, 5 карт, 10 банков у номера.

PUT https://api.apiin.ru/api/organizations/{org_id}/pay-options
Content-Type: application/json

{
  "enabled": true,
  "recipient_name": "Анна Павловна С.",
  "sbp":   { "enabled": true,  "phones": [ { "phone": "8 900 123-45-67", "banks": ["Сбер", "Альфа-Банк"] } ] },
  "cards": { "enabled": true,  "items":  [ { "number": "2200 1234 5678 9019", "bank": "" } ] },
  "link":  { "enabled": false, "url": "" }
}

→ 200 нормализованный объект · 400 { "code": "pay_options_invalid", "message": "Телефон 1: …" }

GET …/pay-options возвращает текущие настройки. Публично они видны в GET /p/r/{slug} полем pay_options (только у самозанятого и только включённые способы с данными, иначе null); QR ссылки — GET /p/r/{slug}/pay-link-qr.png.

ОКПО и дата регистрации

Поля организации okpo (8 цифр у юрлица, 10 у ИП, 14 у обособленного подразделения; "" — очистить, иначе 400 invalid_okpo) и registration_date (YYYY-MM-DD, не в будущем; null — очистить, иначе 400 invalid_registration_date) задаются в PATCH /api/organizations/{org_id} и подставляются при поиске по ИНН (GET /api/dadata/party). В счета и акты они не попадают: их видно только на публичной карточке реквизитов, GET /p/r/{slug} отдаёт их полями okpo и registration_date. Пустые поля на карточке не выводятся, а по дате регистрации там показан возраст организации.

ID участника ЭДО

Поле edo_id у организации и у контрагента (создание и PATCH) — идентификатор у оператора ЭДО (в Диадоке вида 2BM-…): код оператора и ID, 4–46 знаков A–Z a–z 0–9 - . @, без пробелов и подчёркиваний; "" — очистить, иначе 400 invalid_edo_id. Нужен только для имени XML-файла УПД.

Реестр операторов персональных данных

Если организация внесена в реестр операторов ПДн Роскомнадзора, номер и дату можно показать контрагентам. Поля организации: pd_operator_number (группы цифр через дефис, до 20 символов, например 77-21-012345; пробелы по краям обрезаются, "" — очистить, иначе 400 invalid_pd_operator_number), pd_processing_start_date (дата начала обработки ПДн, YYYY-MM-DD, не в будущем и не раньше 1900 года; null — очистить, иначе 400 invalid_pd_processing_start_date) и pd_operator_show (показывать ли их в реквизитах, по умолчанию false). Задаются при создании организации и в PATCH /api/organizations/{org_id}. GET /p/r/{slug} отдаёт поля pd_operator_number и pd_processing_start_date, только если показ включён и номер заполнен; иначе вернётся "" и null. В счета и акты эти данные не попадают.

Контрагент

Документ выставляется существующему контрагенту, поэтому его создают заранее. Обязательны kind (ooo|ip|self_employed|individual) и name; остальные реквизиты — по желанию. Реквизиты по ИНН подставляются двумя способами: одним запросом с флагом enrich_by_inn (см. ниже) либо двухшагово — сначала GET /api/dadata/party?inn=… (вернёт название, тип, КПП, ОГРН, адрес), затем подставьте нужные поля в POST …/counterparties. Банк по БИК — GET /api/dadata/bank?bic=…. Без флага сервер ничего не дозаполняет: вы явно контролируете, какие данные записываются.

POST https://api.apiin.ru/api/organizations/{org_id}/counterparties
Authorization: Bearer apiin_live_xxxxxxxx
Content-Type: application/json

{
  "kind": "ooo",
  "name": "ООО «Ромашка»",
  "inn": "7707083893",
  "kpp": "770701001"
}

В ответе — контрагент с полем id (UUID); его подставляют в счёт.

Чтобы не делать два запроса, можно попросить сервер заполнить реквизиты самому — флаг enrich_by_inn:

POST …/counterparties
{ "enrich_by_inn": true, "inn": "7707083893" }

→ наименование, тип, КПП, ОГРН и юридический адрес подставлены из ЕГРЮЛ/ЕГРИП
  • Заполняются только пустые поля: присланные вами значения важнее справочника и не перезаписываются.
  • При enrich_by_inn поля kind и name можно не присылать — они придут из справочника.
  • Ошибки: 400 inn_required (нет ИНН), 400 bad_inn (не 10/12 цифр), 400 enrich_failed (организация не найдена — контрагент не создаётся), 503 unavailable (справочник временно недоступен).
  • Если предпочитаете контролировать данные вручную, остаётся двухшаговый путь: GET /api/dadata/party?inn=…, затем создание с нужными полями.

Комментарий. Поле comment — свободная внутренняя заметка о контрагенте (до 2000 символов): «платит с задержкой», контакты бухгалтера, договорённости. Она видна только в кабинете и через API организации — в PDF, публичные ссылки и письма контрагенту не попадает (в отличие от notes документа, которое печатается). В PATCH …/counterparties/{id} пустая строка "" очищает заметку, отсутствие поля или null — оставляет как было.

PATCH …/counterparties/{id}
{ "comment": "Оплата только по предоплате, бухгалтер Ирина +7 900 000-00-00" }

PATCH …/counterparties/{id}
{ "comment": "" }   → заметка очищена

Счёт

Цены — целое число в копейках (45 000 ₽ → 4500000). Обязательны kind, counterparty_id и хотя бы одна позиция. Номер присваивается автоматически по формату нумерации организации.

POST https://api.apiin.ru/api/organizations/{org_id}/documents
Authorization: Bearer apiin_live_xxxxxxxx
Content-Type: application/json

{
  "kind": "invoice",
  "counterparty_id": "<uuid контрагента>",
  "vat_mode": "none",
  "with_qr": true,
  "items": [
    { "name": "Консультация", "quantity": 1, "price": 4500000 }
  ]
}

vat_mode: none (без НДС) | usn (УСН, без выделения НДС) | osno (НДС «в том числе» — выделяется из цены по ставке каждой позиции vat_rate: 0/5/7/10/20/22, основная с 2026 года — 22; ставка 0 печатается «0%», null — «Без НДС»; если на ОСНО ни у одной позиции нет ставки и ручного НДС нет, строка итогов — «НДС: Без НДС», как в XML). Ставка по умолчанию для новых позиций — default_vat_rate организации (PATCH; null — убрать, тогда 22 % на ОСНО и 5 % на УСН с НДС). Поле manual_vat_amount (копейки, опционально) переопределяет отображаемую сумму НДС при ОСНО, не меняя total; null возвращает авторасчёт. Налог, который стоит в печатной форме, QR, XML и выгрузке, — поле ответа vat_amount_effective: ручная сумма, если она действует (ОСНО, не УПД и не счёт-фактура, не больше итога), иначе vat_amount. Сам vat_amount всегда расчётный; сумма без налога «как в печати» — total − vat_amount_effective. У счёта-фактуры и УПД (форма 1137) НДС по умолчанию считается «сверху» (цены без налога, налог по ставке строки). prices_include_vat: true (только у upd и invoice_factura, иначе 400 prices_include_vat_not_applicable) — «цены включают НДС», как в 1С: цены с налогом, НДС «в том числе» по той же формуле, что у счёта, поэтому итог и налог совпадают со счётом копейка в копейку; в печатной форме гр.5 = сумма − налог, гр.8 = налог, гр.9 = сумма, гр.4 = гр.5 / количество (округлено до копейки — при количестве больше одного гр.3 × гр.4 может отличаться от гр.5 на копейки). Смена флага в PATCH пересчитывает итоги (цены не пересчитываются — итог меняется на сумму налога; выключить режим, пока в позициях есть скидки, нельзя — 400 discount_not_applicable). У строгих форм ручного НДС нет — налог считается по строкам (гр.8): manual_vat_amount у upd/invoice_factura — 400 manual_vat_not_applicable (в PATCH можно передать прежнее значение или null). Поэтому УПД из счёта с ручным НДС берёт налог по строкам — он может отличаться от ручного на копейки. Графы 7–8 печатной формы: строка облагается — ставка и сумма налога (ставка 0% печатается «0%» и «0,00»), не облагается — «без НДС» в обеих графах; в строке «Всего к оплате» гр.8 — «без НДС», если без НДС все строки, иначе сумма налога. Без режима «цены включают НДС» налог начисляется по ставке строки при любом vat_mode (организация не на ОСНО, выставившая счёт-фактуру с НДС, этот налог уплачивает); в режиме «цены включают НДС» — как у счёта: выделяется только при osno. Позиция принимает необязательный product_id — товар каталога, из которого она взята. Это только пометка: наименование, цена, единица и ставка остаются снимком и по ссылке не перечитываются (документ — юридическая запись), а кабинет по ней показывает «товар в каталоге изменился, обновить позицию?». Чужой товар — 400. Позиция принимает необязательный discount_percent — скидку в процентах (0..100, можно дробную: 7.5). Она уменьшает сумму строки, цена печатается прежней, а рядом появляется колонка «Скидка»; НДС считается уже от суммы со скидкой. Скидка допустима в invoice, invoice_contract, act и report, а в счёте-фактуре и УПД — только при prices_include_vat: true (графы «скидка» в форме нет — печатается цена после скидки); иначе — 400 discount_not_applicable, указывайте цену уже со скидкой. УПД или счёт-фактура, созданные из счёта, акта или другого документа с ценами «с НДС» (derive), получают prices_include_vat: true сами: цены и скидки копируются как есть, итог и НДС — как у источника. Параметра «создать без режима» у derive нет намеренно: пересчёт цен с НДС в цены без налога даёт расхождение с источником на копейки (налог сверху от округлённой цены), и счёт оставался бы «частично закрыт»; печатная форма в обоих режимах одна (гр.4 — цена без налога). Нужны цены без НДС — создайте УПД через POST …/documents со своими позициями и prices_include_vat: false. Из УПД/СФ с ценами без налога в счёт цена пересчитывается в цену с НДС (точно при делимости суммы на количество). Изменение поведения (27.10.2026): раньше УПД из счёта получал те же цены, но как «без НДС», и итог был больше счёта на сумму налога. Если интеграция правит позиции такого УПД через PATCH, передавайте цены с НДС (или выключите режим prices_include_vat: false — цены при этом не пересчитываются, итог изменится на налог). Массовые действия над документами — POST …/documents/bulk с телом {"ids": [...], "op": "<операция>", ...}. Операции: counterparty, org_bank_account, issue_date, due_date, order, tags, template, style, language, payment, sign, cancel, derive_act, delete. Ответ — {updated, skipped[{id, reason}], created[]}: одна неподходящая строка (аннулированный документ, акт в операции оплаты) не отменяет остальные, а возвращается с причиной. За вызов — не больше 200 документов. Права те же, что у поштучных действий: оплата — payments:write, подпись — documents:sign, удаление — documents:delete. В выгрузках export.xlsx/export.csv есть параметр ids (uuid через запятую) — выгрузить ровно выбранные документы. Необязательное поле organization_bank_account_id — ваш расчётный счёт (из GET …/bank-accounts), на который принимается оплата по этому документу: его реквизиты печатаются в банк-шапке и подставляются в платёжный QR. Не передан — сервер закрепляет за документом счёт «по умолчанию» на момент создания, поэтому последующая смена счёта по умолчанию уже выставленные документы не меняет. В PATCH значение null возвращает документ к текущему счёту по умолчанию (и тут же закрепляет его). Счёт обязан принадлежать вашей организации. Необязательное поле counterparty_bank_account_id — банковский счёт контрагента (из его карточки, GET …/counterparties/{id}/bank-accounts): его реквизиты печатаются в «Реквизитах сторон» договора и подставляются в переменные тела {{cp_bank}}, {{cp_bik}}, {{cp_account}}, {{cp_corr_account}}. Не передан — берётся счёт контрагента «по умолчанию». Счёт чужого контрагента — 400; в PATCH значение null возвращает счёт по умолчанию, а смена counterparty_id с «зависшим» счётом отклоняется. Виды документов (kind): invoice (счёт), act (акт), invoice_contract (счёт-договор), invoice_factura (счёт-фактура), contract (договор), report (отчёт о выполненных работах), agent_report (отчёт агента), upd (универсальный передаточный документ: upd_status 1 — счёт-фактура и передаточный, 2 — только передаточный; не передан — 1 на ОСНО или при ставке больше нуля в строке, иначе 2; сменить можно и в PATCH), power_of_attorney (доверенность М-2/М-2а или свободная, см. ниже). Наименование позиции (items[].name) — до 2000 символов, как в формате ФНС: в строку помещаются маршрут, ТТН, машина и водитель. В ответе — документ с id, номером и рассчитанными суммами (subtotal, vat_amount, total) и налогом как в печати — vat_amount_effective.

Доверенность (М-2 / М-2а и свободная)

Создаётся тем же POST …/documents с kind: "power_of_attorney" и блоком poa. Поставщик — counterparty_id, перечень ценностей — items без цен (цена, НДС и скидка у доверенности обнуляются).

POST …/documents
{
  "kind": "power_of_attorney",
  "counterparty_id": "…",            // поставщик
  "issue_date": "2026-10-06",
  "poa": {
    "form": "m2",                    // m2 (с корешком) | m2a
    "valid_until": "2026-10-21",     // по умолчанию +15 дней, не больше года
    "employee_id": "…",              // сотрудник справочника — ИЛИ representative
    "representative": { "full_name": "Петров Пётр Петрович", "position": "Водитель",
      "passport_series": "45 08", "passport_number": "123456",
      "passport_issued_by": "…", "passport_issued_at": "2015-03-12",
      "passport_division_code": "770-001" },
    "basis": { "title": "Счёт", "number": "125", "date": "2026-10-01" }
  },
  "items": [ { "name": "Бумага А4", "unit": "пач", "quantity": "10" } ]
}
  • Паспорт необязателен: незаполненные поля печатаются линиями для заполнения от руки. По employee_id сервер сам делает снимок данных сотрудника — правка справочника выданную доверенность не меняет.
  • Перечень можно не передавать, если указано основание (basis), иначе 400 poa_items_or_basis_required. Количество печатается прописью.
  • Статус: POST …/documents/{id}/poa/used (тело {title, number, date} — документ поставщика), …/poa/revoke ({date, reason}) и …/poa/reissue. «Просрочена» вычисляется по valid_until. Вебхук — document.poa_status_changed.
  • У доверенности нет оплаты, QR, публичной ссылки и доступа по email (в ней паспорт), производных документов и повторов. «Создать подобный» (…/copy) выпускает новую с той же длительностью срока и свежими данными сотрудника.
  • Для М-2/М-2а поставщик обязателен (400 poa_supplier_required). У прочих видов документов counterparty_id по-прежнему обязателен (400 counterparty_required); в ответе у доверенности без адресата — null.

Свободная доверенность

"form": "free" — полномочия текстом вместо перечня ценностей: template (ключ шаблона), powers (текст; не передан — берётся текст шаблона), place (место выдачи), substitution (право передоверия). counterparty_id необязателен — это адресат «кому предъявляется». items не передаются (400 poa_free_no_items). Срок по умолчанию — год, максимум — три года. Каталог шаблонов — GET /api/poa-templates (без авторизации): key, title, powers, hint.

"poa": { "form": "free", "template": "bank", "place": "г. Москва",
         "substitution": false, "employee_id": "…" }

Правка — PATCH …/documents/{id} с теми же полями poa: пропущенное поле не меняется, пустая строка в place/template очищает; смена template без powers подставляет текст нового шаблона (у custom текста нет — остаётся прежний), повторная отправка того же шаблона правленый текст не трогает. Заказа и счёта контрагента у доверенности нет: order_id и counterparty_bank_account_id игнорируются. "counterparty_id": null снимает адресата свободной доверенности (у М-2/М-2а — 400 poa_supplier_required, у прочих видов — 400 counterparty_required). Перевод свободной без адресата в М-2 тоже требует поставщика.

Список, фильтры и журнал

  • GET …/documents?kind=power_of_attorney и фильтры: poa_status=issued|used|revoked|expired (на сегодня по Москве, без аннулированных), employee_id, poa_form=m2|m2a|free. exclude_kind=power_of_attorney — реестр без доверенностей. Те же фильтры действуют в сводке и выгрузке реестра.
  • Журнал учёта: GET …/poa-journal.pdf и GET …/poa-journal.xlsx с теми же фильтрами (период — date_from/date_to). Право — documents:read. Больше 10 000 строк — 400 poa_journal_too_large (сузьте период); PDF — до 2 000 строк (400 poa_journal_too_large_pdf, длинную выборку берите в Excel). Тяжёлый отчёт — см. «Ошибки» (очередь и лимит).

Справочник сотрудников

GET/POST …/employees, GET/PUT/DELETE …/employees/{id}, POST …/employees/{id}/archive|unarchive, PUT …/employees/{id}/account { "developer_id": … | null } — сотрудник и участник кабинета один человек (привязать участника организации к карточке или отвязать — владелец или администратор в кабинете, иначе 403 account_admin_only; так же правка, архив и удаление уже привязанной карточки; один аккаунт — одна карточка, иначе account_has_employee). Одно действующее приглашение на сотрудника (employee_invite_pending). В кабинете доступ сотруднику выдаётся приглашением с его карточкой — после входа она привязывается сама. Карточка: ФИО по частям, должность, паспорт, СНИЛС и ИНН (проверяются контрольные суммы). Паспорт — персональные данные: полные карточки — по праву employees:read, правка — employees:write; право employees:use — выбрать сотрудника в доверенности (его данные с паспортом подставит сервер, иначе 403 employees_use_forbidden — тогда representative вручную) и получить краткий список без паспорта (?brief=1 — так же при любом праве). У ролей employees:write ⊃ employees:read ⊃ employees:use. Главный бухгалтер для подписи в М-2, счёте-фактуре и УПД — подписант организации (раздел ниже).

Подписанты

Кто подписывает документы организации: руководитель (ровно один, у ИП — сам предприниматель), главный бухгалтер (не больше одного, не у ИП, самозанятого и физлица; документы не подписывает и подписантом по умолчанию не бывает) и представители по доверенности. GET …/signers (?archived=1 — с архивными; право organizations:read или documents:read), POST …/signers (role: chief_accountant | representative, is_default), GET/PUT/DELETE …/signers/{id} (PUT — полная замена карточки), POST …/signers/{id}/archive|unarchive|default; правка — право organizations:write. POST …/signers/preview — пробный расчёт без записи (право на чтение): тело — role, поля карточки и signer_id у сохранённой; ответ — как напечатается (position, initials, poa_line, strict_poa_line — в УПД и счёте-фактуре, in_person, basis_text, signature_scale — масштаб факсимиле), автозначения пустых полей (auto) и поля, которые сохранение пересчитает после смены ФИО или должности (refreshed). GET …/signers/{id}/signature — картинка факсимиле карточки по праву на чтение (библиотека ассетов для этого не нужна). Карточка: last_name, first_name, middle_name, head_type (только руководитель), position (пусто — по роли и типу руководителя), position_genitive и name_genitive («в лице генерального директора Иванова Ивана Ивановича»; пусто — подставятся для типовых должностей и склонятся), signature_name (пусто — «Иванов И. И.»), basis («Устава»), у представителя — poa_number, poa_date (обязательны) и poa_valid_until (номер — до 50 знаков, даты — 1900–2099), poa_document_id (доверенность из журнала), а также employee_id и signature_asset_id — факсимиле подписи этого человека, у любой роли (ассет вида signature, иначе 400 signer_signature_invalid): печатается над его подписью, у главбуха — над строкой «Главный бухгалтер» в УПД, счёте-фактуре и доверенности М-2; без своего факсимиле подпись ставится от руки. Файл подписи, который есть в выданных документах, не удаляется (DELETE …/assets/{id} → 409 asset_in_use): уберите его из карточки — документы сохранят подпись. Смена человека через PATCH организации (новая фамилия руководителя или главбуха; инициалы и дописанное отчество — не смена) без новой signature_asset_id снимает факсимиле прежнего; PUT карточки — полная замена, подпись в нём как прислана. В организации signature_asset_id — только файл вида signature (иначе 400 signer_signature_invalid). При смене ФИО или должности «в лице …» и расшифровка, совпадавшие с автоматическими, пересчитываются. В ответе — готовые надписи печати: position_label, initials, poa_line, in_person, basis_text, poa_expired. Руководителя нельзя убрать в архив или удалить (signer_head_required) — смена директора правкой его карточки; карточку, указанную в документах, — только в архив (409 signer_in_use). Поля руководителя и главбуха в организации (head_*, director_name, signer_name, chief_accountant_name, signature_asset_id — факсимиле руководителя) — зеркало этих карточек: PATCH организации с ними правит карточки (присланное поле важнее остальных; фамилия обязательна для ФИО, части ФИО — до 200 знаков; смена вида организации сбрасывает неподходящий тип руководителя).

В документе — signer_id и (у УПД, счёта-фактуры и доверенности) accountant_id; не переданы при создании — подписант по умолчанию и действующий главбух (доверенность без выбора — руководитель). Документ хранит signers — снимок данных на момент выбора (у представителя — и доверителя): печать, Word и XML берут его, правка карточки выданные документы не меняет; в PATCH тот же id берёт данные заново. Подписывает руководитель или представитель (доверенность — только руководитель), вторую подпись — главбух или представитель (иначе 400 invalid_signer; то же у правил повтора, а при копии и «на основе» неподходящая карточка заменяется подписантом по умолчанию). accountant_id у вида без второй подписи — 400 invalid_signer. Доверенность представителя не действует на дату документа — документ сохраняется, в ответе заголовок X-Apiin-Warnings: signer_poa_expired. У представителя под подписью печатается «по доверенности № … от …» (в УПД и счёте-фактуре — «иное уполномоченное лицо, по доверенности № … от …», так же у главбуха-представителя), в XML — СпосПодтПолном=5 и СвДоверБум.

Собственная нумерация

Если у вас своя сквозная нумерация, передайте number прямо при создании — он будет использован вместо номера по формату организации:

POST …/documents
{ "kind": "invoice", "counterparty_id": "…", "number": "CR-2026/08-0042", "items": [ … ] }

POST …/documents/{id}/create-act
{ "number": "ACT-2026/08-0042" }        // тело необязательное
  • Внутренний счётчик сдвигается на каждый документ, даже если номер вы задали сами: порядковый ключ документа остаётся монотонным. От столкновения с занятыми вручную номерами защищает другой механизм — автонумерация их пропускает (см. ниже). Поэтому после серии собственных номеров счётчик обгоняет вашу нумерацию: вернуть его на место помогает next_seq (см. «Продолжить чужую нумерацию»).
  • Номер обязан быть свободным. Попытка выставить документ с номером, который уже занят, возвращает 409 с кодом number_taken — и при создании, и при PATCH. Пустой номер — 400 bad_number.
  • Уникальность считается в пределах вида документа, серии нумерации и года выпуска, среди действующих документов. Серия — это то, на каком уровне задан формат номера: аккаунт, контрагент или заказ. Поэтому счёт № 1 и акт № 1 не конфликтуют; у двух контрагентов с собственной нумерацией «1» тоже свои серии; а после сброса нумерации по году номер повторяется законно.
  • Аннулированный документ освобождает номер — типовой сценарий «ошиблись → аннулировали → выставили заново тем же номером».
  • Если номер не задан, сервис выдаёт следующий по формату и пропускает занятые вручную значения, поэтому автоматическая нумерация не спотыкается о ваши номера.
  • Сетевой ретрай с тем же Idempotency-Key конфликта не вызывает: повтор вернёт уже созданный документ (200), а не 409.
  • Номер можно изменить и позже — PATCH …/documents/{id} с полем number.
  • Найти документ по номеру: GET …/documents?number=145 (точное совпадение, можно вместе с kind) — удобно для сверки «уже перенесён?».
  • Продолжить чужую нумерацию. После переноса истории из другого сервиса задайте, с какого номера пойдут новые документы: PUT …/numbering с телом { "kind": "invoice", "next_seq": 1201 } — следующий счёт получит номер 1201. Там же необязательные format (шаблон номера) и reset_yearly; не переданные поля не меняются. Плейсхолдеры формата — {n}, {n:05} (с ведущими нулями), {YYYY}, {YY}, {MM}; регистр важен, неизвестный плейсхолдер останется в номере как есть. Текущее состояние — GET …/numbering (поле current_value — последний выданный номер).
  • Серии контрагента и заказа. Если у контрагента или заказа задан свой numbering_format, его документы идут в отдельную серию. Тогда next_seq и dismiss_hint_for передавайте вместе с counterparty_id (или order_id), а формат такой серии меняйте на самом контрагенте (PATCH …/counterparties/{id}) — format и reset_yearly вместе с counterparty_id вернут 400. GET …/numbering показывает только общую серию организации.
  • Подсказка «продолжить нумерацию». GET …/numbering/suggest?kind=invoice&counterparty_id=…&order_id=… определяет серию, в которой окажется документ (заказ → контрагент → организация), и возвращает: scope и scope_ref — фактическую серию (может оказаться account, если у контрагента своего формата нет), format, next_auto — номер, который выдаст автонумерация, last — самый свежий документ серии, reference — документ с максимальным номером серии за текущий год (номер уникален в пределах года), suggestion — next_seq и три следующих номера, dismissed — отказывались ли уже от этой подсказки.
  • suggestion появляется, когда номер ориентира задан вручную (отличается от того, что дал бы формат), продолжение отличается от автоматического, а сам предложенный номер свободен. Направление любое: счётчик мог убежать вперёд (перенос базы: 808 документов, а реальные номера — до 59) и отстать (номер задали с запасом). Номера, не разбираемые текущим форматом, в расчёт не идут. Ориентир ищется среди 500 документов серии за текущий год.
  • Принять подсказку — PUT …/numbering с next_seq; отказаться — там же { "kind": "invoice", "dismiss_hint_for": "<id ориентира>" }: подсказка замолчит, пока не появится новый ручной номер. Ошибки: 400 bad_next_seq (next_seq меньше 1), 400 «Контрагент (Заказ) не найден в этой организации», 400 «Документ для dismiss_hint_for не найден», 404 «В этой серии ещё нет ни одного документа», 400 «Укажите хотя бы одно из полей».

Колонки «Кол-во» и «Ед.»

У услуг количество почти всегда 1, а единица пустая — две колонки занимают место, которого не хватает наименованию. Флаги hide_quantity_if_unused и hide_unit_if_unused (у документа; значения по умолчанию — в организации, PATCH …/organizations/{id}) убирают колонку из PDF, Word и публичной страницы, только пока она не заполнена: количество во всех позициях равно 1, единица везде пустая. Стоит указать количество или единицу хотя бы в одной позиции — колонка появляется, флаг не мешает. Цена остаётся всегда. Действует на счёт, счёт-договор, акт и отчёт о работах; счёт-фактура, УПД (утверждённая форма 1137) и отчёт агента печатаются без изменений (для акта скрытие законно: 402-ФЗ ст. 9 требует натуральное и (или) денежное измерение).

Оформление документа — сразу при создании

Все настройки вида документа принимаются тем же запросом POST …/documents, второй вызов PATCH не нужен:

{
  "kind": "invoice",
  "counterparty_id": "<uuid контрагента>",

  "with_qr": true,             // платёжный QR (по умолчанию true)
  "with_signatures": true,     // факсимиле подписи и печати (по умолчанию true)
  "with_sign_field": true,     // поле «подпись/печать» под документом; false — для ЭДО

  "hide_logo": true,           // не печатать логотип В ЭТОМ документе
  "hide_stamp": false,         // не печатать печать
  "hide_signature": false,     // не печатать факсимиле подписанта и главбуха

  "hide_quantity_if_unused": true, // без колонки «Кол-во», если везде количество 1
  "hide_unit_if_unused": true,     // без колонки «Ед.», если единица не заполнена

  "logo_asset_id": null,       // взять ДРУГОЙ ассет вместо дефолта организации
  "stamp_asset_id": null,

  "invoice_style": "individual",
  "accent_color": "#2563EB",
  "tag_ids": ["<uuid тега>"],
  "items": [ … ]
}
  • hide_logo / hide_stamp / hide_signature — «явно без ассета» для конкретного документа: сильнее и дефолта организации, и *_asset_id. Флаги независимы, поэтому «счёт без логотипа, но с печатью и подписью» — это hide_logo: true при with_signatures: true. Факсимиле подписи — не ассет документа, а данные подписанта: берётся из его снимка в документе (signers), в документе его можно только скрыть (hide_signature).
  • with_signatures: false убирает подпись и печать разом (общий выключатель), hide_* — точечно.
  • tag_ids проставляются при создании. Все теги должны принадлежать организации, иначе 400 с кодом tag_not_found (документ не создаётся) — берите UUID из GET …/tags. Изменить набор позже: PUT …/documents/{id}/tags.
  • Опущенные logo_asset_id / stamp_asset_id, invoice_style, accent_color берутся из настроек организации, как и hide_quantity_if_unused / hide_unit_if_unused. У остальных флагов дефолт фиксированный: with_qr / with_signatures / with_sign_field — true, hide_logo / hide_stamp / hide_signature — false.
  • Те же поля принимает PATCH …/documents/{id}, если оформление меняется потом. Неизвестные поля тела запрос не отклоняют — они молча игнорируются, поэтому опечатка в имени поля не даст ошибки: сверяйте результат по ответу (201 возвращает документ целиком).
  • Копия (POST …/documents/{id}/copy) и производный документ (…/create-act) наследуют оформление, скрытые ассеты и теги источника.

PDF, Word, XML для ЭДО и публичная ссылка

  • PDF: GET …/documents/{id}/pdf (добавьте ?download=1 для вложения);
  • Word: GET …/documents/{id}/docx;
  • XML формата ФНС для ЭДО: GET …/documents/{id}/xml — акт и УПД в формате УПД 5.03 (схема ON_NSCHFDOPPR_1_997_01_05_03_05, windows-1251). Акт и УПД со статусом 2 выгружаются функцией ДОП (отдельного формата акта с 2026 года нет), УПД со статусом 1 — СЧФДОП. Файл загружают к своему оператору ЭДО и подписывают КЭП там. Имя файла из Content-Disposition равно атрибуту ИдФайл — сохраняйте его без переименования, иначе оператор отклонит файл. В имени — ID участников ЭДО организации (отправитель) и контрагента (получатель) из полей edo_id; не заданы — заглушка «не через оператора» (000 + GUID). Основания передачи — из поля basis: каждый документ с номером и датой («Договор № 5 от 01.01.2026, спецификация № 2 от 03.02.2026» — два основания; номер можно после даты, без номера — «Без номера»; пояснения в скобках пропускаются), иначе счета, которые документ закрывает. Часть поля не разобрана (документ без даты, хвост без номера) — файл отдаётся с заголовком X-Apiin-Warnings: basis_unparsed (дата основания в формате обязательна). Суммы — как в печатной форме, включая ручной НДС акта (раскладывается по облагаемым строкам; больше их суммы — xml_manual_vat); ставка 0% — «0%», позиция без ставки — «без НДС», как в печатной форме. Позиция без единицы измерения выгружается суммами, без количества и цены (ОКЕИ в формате обязателен при натуральном измерителе). Ошибки 400: xml_not_supported (другой вид), xml_cancelled, xml_no_counterparty, xml_no_items, xml_number (нет номера), xml_date (дата вне 1900–2099), xml_item_name (позиция без наименования), xml_totals (сумма позиций не сходится с итогом — старые данные, пересохраните документ), xml_party_inn / xml_party_fio / xml_party_name (реквизиты стороны), xml_party_address (адрес нужен для статуса 1), xml_signer (ФИО руководителя), xml_foreign_party, xml_vat_rate, xml_negative_line, xml_currency, xml_manual_vat (ручной НДС без облагаемых строк или больше их суммы). xml_not_supported, xml_cancelled и xml_no_counterparty приходят сразу; остальные проблемы — реквизиты документа, сторон, подписанта и каждой позиции — одним ответом: message перечисляет их через «; », code — первой (ручной НДС и итог проверяются, когда все позиции в порядке). Право documents:read;
  • Публичная ссылка на счёт: POST …/public-links с телом { "target": "document", "target_ref_id": "<id счёта>" } — в ответе url неугадываемой страницы (с QR и реквизитами). Вместо target_ref_id принимается алиас document_id — оба варианта равнозначны.

Оплата, акт, подпись

  • Статус оплаты выводится из привязок денег (раздел «Деньги»): сумма привязок против итога даёт unpaid / partial / paid, а paid_at — дата последней привязки (полночь UTC). Поэтому правка позиций не сбрасывает оплату: если итог вырос, счёт честно становится «частично оплачен».
  • Отметить оплату полностью: POST …/documents/{id}/mark-paid — создаёт привязку «отметка» (kind: "mark") на остаток долга; идемпотентен (у оплаченного ничего не добавляет, повторного события вебхука нет). Тело необязательно: { "paid_at": "ГГГГ-ММ-ДД" } — дата оплаты задним числом; у уже оплаченного меняет дату только у привязок-отметок, реальные платежи не трогает. POST …/mark-unpaid снимает все привязки документа (операции в «Деньгах» остаются и снова считаются непривязанными). Если счёт оплачен платежом из «Денег», нужно ещё money:write (403 money_write_required) — так же и в массовой отметке. Признак такого счёта — поле документа paid_by_operation (только для чтения, есть и в списке);
  • Оплата суммой: POST …/documents/{id}/payments с { "amount": <копейки>, "paid_at": "ГГГГ-ММ-ДД" } — привязка без платежа (kind: "manual"). GET …/payments и поле payments документа возвращают все привязки: у каждой kind (operation — реальный платёж из «Денег», manual, mark, legacy — перенесено) и operation_id. Сумма «платежей» — это не обязательно реальные деньги: различайте по kind;
  • Дата оплаты без платежа: PATCH …/documents/{id}/payments/{payment_id} с { "paid_at": "ГГГГ-ММ-ДД" } (право payments:write) — для manual, mark и legacy; сумма и статус не меняются, вебхука нет, paid_at документа пересчитывается (для синхронизации — по updated_since). Та же дата — 200 без изменений. У платежа из «Денег» дата — дата операции: 400 payment_has_operation; аннулированный документ — 400 document_cancelled, не счёт — 400 document_not_payable, год вне 2000–2100 — 400 invalid_date, нет такой оплаты у документа — 404. Нужна, когда оплату отметили днём внесения, а деньги пришли раньше: в акте сверки дата оплаты — ключевая графа. Сумма оплаты (POST …/payments) — не больше 1 трлн ₽ (400 amount_too_large);
  • Оплата с выбором платежа: GET …/documents/{id}/allocation-candidates — поступления, подходящие счёту, с рангом (exact / likely / weak) и причиной; POST …/documents/{id}/allocations { "items": [{ "operation_id": "…", "amount": … }, { "amount": …, "paid_on": "…" }] } — от 1 до 20 строк (bad_items), платежи и/или «без платежа». Запрос выполняется целиком или никак: отказ любой строки не записывает остальные. Сумма «без платежа» — не больше остатка долга (400 amount_exceeds_due), по умолчанию — весь остаток; оплаченный счёт — nothing_to_allocate. Платёж из «Денег» требует ещё money:write. DELETE …/documents/{id}/allocations/{aid} — отвязать (платёж из «Денег» — с правом money:write);
  • Акт на основе счёта одним вызовом: POST …/documents/{id}/create-act — позиции и контрагент копируются из счёта, основание проставляется «По счёту № … от …», связка «счёт ↔ акт» создаётся автоматически. Тело необязательно: { "number": "…", "issue_date": "ГГГГ-ММ-ДД" } — свой номер и дата акта. Повторный вызов вернёт 200 и уже созданный акт как есть — переданные number и issue_date при этом игнорируются (меняйте их через PATCH);
  • Подписать закрывающий документ (акт, УПД, отчёт): POST …/documents/{id}/sign (другие виды — 400 sign_kind_mismatch). Связка закрывается сама, когда счёт оплачен, а документ подписан;
  • Закрывающие документы: УПД и отчёт, созданные из счёта (POST …/documents/{id}/derive?kind=upd|report), связываются с ним так же, как акт; повтор возвращает уже созданный действующий документ того же вида. Ручная связь — POST …/document-links {"invoice_id", "act_id"}, где act_id — акт, УПД или отчёт (имя поля историческое): оба одного контрагента, действующие, в одной валюте, иначе 400 link_kind_mismatch | link_counterparty_mismatch | link_cancelled | link_currency_mismatch, повтор — link_exists. Необязательное amount — на какую сумму документ закрывает счёт (копейки; не передано — весь документ, и закрытая сумма следует за его итогом): не больше итога документа и незакрытого остатка счёта и не больше той части документа, что ещё не закрывает другие счета (link_amount_invalid, link_amount_exceeds_unclosed, link_amount_exceeds_document); документ, уже закрывающий другой счёт, требует суммы (link_amount_required); к частично закрытому счёту документ больше остатка привязывается на остаток. Вклад связи — не больше итога документа: уменьшили акт — счёт снова закрыт частично. Счёт, закрытый целиком, новых закрывающих документов не принимает — 409 invoice_already_closed: ручная связь, derive, create-act (повтор возвращает уже созданный акт), восстановление аннулированного закрывающего документа. Новый закрывающий документ к частично закрытому счёту (derive, create-act) закрывает только остаток; массовое «создать акт» счета с закрывающими документами пропускает. Восстановить аннулированный закрывающий документ, который вместе с другими документами счёта закрыл бы его сверх суммы, нельзя — 409 restore_exceeds_unclosed (снимите связь, восстановите документ и привяжите его на остаток). POST …/documents/{id}/derive?kind=act|upd|report без флага возвращает уже созданный действующий документ того же вида (ретрай не плодит отгрузки); &additional=1 — ещё один к частично закрытому счёту: позиции копируются целиком, а связь закрывает только остаток — уменьшите позиции до фактически переданного. Ошибочную связь снимает DELETE …/document-links/{id} (документы остаются). Создание, снятие и закрытие связи пишутся в журнал счёта (document.closing_linked, document.closing_unlinked, document.closing_link_closed; document.link_created — это публичная ссылка);
  • Аннулировать документ: POST …/documents/{id}/cancel (идемпотентно) — документ получает cancelled_at, в PDF появляется пометка «АННУЛИРОВАН», платёжный QR скрывается, а оплата/подпись/отправка и правка (PATCH, в том числе массовая) отвечают 400 с кодом document_cancelled. Исключение — перенос в заказ (PATCH только с order_id): заказ лишь группирует реестр. Теги меняются как обычно. Чтобы исправить документ, восстановите его или создайте подобный (…/copy). Восстановление — POST …/uncancel. События вебхука: document.cancelled / document.uncancelled. Нюанс: если ссылка онлайн-оплаты была создана ДО аннулирования и покупатель по ней заплатил, платёж фиксируется (деньги поступили — запись нужна для сверки); владелец видит противоречие и решает — вернуть платёж или восстановить документ.

Акт сверки

Акт сверки взаимных расчётов с контрагентом собирается на лету из документов и денег — в реестре он не хранится, номера не получает. Права: documents:read + money:read (в акт входят поступления без привязки к счетам и расчёты с поставщиком). Суммы — в копейках; сальдо со знаком: плюс — контрагент должен вам, минус — вы ему.

GET …/counterparties/{id}/reconciliation?date_from=2026-10-01&date_to=2026-10-31
GET …/counterparties/{id}/reconciliation.pdf?…&signatures=0&mirror=1&download=1
GET …/counterparties/{id}/reconciliation.xlsx?…&mirror=1

→ {
  "organization": { "id": "…", "name": "ИП Иванов И. И.", "inn": "…", "kpp": null },
  "counterparty": { "id": "…", "name": "ООО «Ромашка»", "inn": "…", "kpp": "…" },
  "date_from": "2026-10-01", "date_to": "2026-10-31", "basis": "shipments",
  "opening":  { "balance": 0, "debit": 0, "credit": 0 },
  "rows": [
    { "date": "2026-10-04", "title": "Акт № 2 от 04.10.2026", "debit": 2776400, "credit": 0 },
    { "date": "2026-10-05", "title": "Оплата (№ 118 от 05.10.2026)", "debit": 0, "credit": 2000000 }
  ],
  "turnover": { "debit": 2776400, "credit": 2000000 },
  "closing":  { "balance": 776400, "debit": 776400, "credit": 0 },
  "conclusion": "По данным … на 31.10.2026 задолженность в пользу … составляет 7 764,00 руб. (…)",
  "opening_balance": null,
  "warnings": [{ "code": "invoices_without_shipment", "text": "…" }]
}
  • Период — обе даты обязательны (400 period_required, пустая строка — как отсутствие); 400 invalid_period — дата не в формате ГГГГ-ММ-ДД или несуществующая, «с» позже «по», год вне 2000–2100 или период длиннее 5 лет; basis — только shipments / invoices (400 invalid_basis). Потолок — 10 000 строк (400 reconciliation_too_large), у PDF — 2 000 (400 reconciliation_too_large_pdf: сузьте период или возьмите Excel). Период целиком раньше даты начального остатка — 400 period_before_opening_balance (записи до неё учтены в остатке). Сальдо на начало считается за всю историю до периода.
  • download=1 отдаёт PDF вложением (иначе — показ в браузере); Excel всегда вложением. Имя файла — «Акт сверки с … за ДД.ММ.ГГГГ–ДД.ММ.ГГГГ».
  • Дебет (долг контрагента растёт): при basis=shipments (по умолчанию) — ваши акты, УПД и отчёты, как в учёте бухгалтера контрагента; при basis=invoices — счета и счета-договоры. Режим касается только ваших документов. Счёт-фактура, договор и доверенность в акт не входят. Плюс ваши оплаты поставщику и возвраты покупателю (списания типов «Оплата поставщику», «Возврат покупателю», «Прочее», «Не разобрано»), оплаты документов поставщика без платежа и любое списание, привязанное к документу этого поставщика (даже «Комиссия банка»).
  • Кредит (долг уменьшается): поступления от контрагента (операции «Денег» типов «Оплата от покупателя», эквайринг, возврат от поставщика, «Прочее», «Не разобрано»), оплаты счетов без платежа (manual/mark/legacy, по дате оплаты) и документы поставщика из «Денег» с указанным контрагентом (акт, накладная, УПД, чеки, прочие; счёт поставщика — нет ни в одном режиме) — встречные расчёты. Займы, переводы между своими счетами, налоги и зарплата — не взаиморасчёты.
  • Операция «Денег» считается один раз: у операции с этим контрагентом — вся сумма (за вычетом привязанного к документам других контрагентов), у любой другой операции (без контрагента или с другим) — только привязанное к документам этого. Аннулированные документы и их оплаты без платежа, документы в валюте и операции по валютным счетам не учитываются.
  • Предупреждения (warnings, в файлы не печатаются): invoices_without_shipment — счета периода без связанного акта; unknown_operations — учтены операции «Не разобрано»; unlinked_by_inn — деньги с ИНН контрагента, не попавшие в акт: непривязанный остаток операций без контрагента и операции другой карточки с тем же ИНН; foreign_currency, foreign_currency_operations; possible_double_payments — поступления не привязаны к счетам, а оплаты отмечены вручную (одни и те же деньги могли учесться дважды; скрытые в «Деньгах» поступления не считаются); cancelled_payments — оплаты аннулированных документов не учтены; opening_inside_period. Деньги вне акта и двойной учёт проверяются за всю историю после даты остатка — они искажают и сальдо на начало.
  • PDF — по стандартной форме: две половины («по данным организации» заполнена, «по данным контрагента» — для него), итоговая фраза с суммой прописью, подписи; печать организации и факсимиле подписанта — по умолчанию, signatures=0 — без них. Excel — тот же макет, суммы числами.
  • mirror=1 (PDF и Excel) — половина «по данным контрагента» заполнена зеркально вашими строками: те же даты и документы, дебет и кредит переставлены, операции названы с его стороны (ваш «Акт № 5» — его «Поступление (акт № 5 …)», ваша «Оплата» — его «Оплата поставщику», ваше «Поступление (акт № 7 …)» — его «Акт № 7», возвраты меняются местами), плюс итоговая фраза «По данным контрагента…». Контрагенту остаётся проверить и подписать — так акт отправляют по ЭДО или в оригинале. Данные организации контрагента (если она тоже в Apiin) не используются.

Двойной учёт. replacements[] — пары «непривязанный платёж ↔ оплаты счёта без платежа» того же контрагента, похожие на одни и те же деньги (то же правило, что при импорте выписки; окно — от даты начального остатка до конца периода; пары, отклонённые «Не то», не показываются): operation_id, op_date, op_free (свободная часть платежа), document_id, document_number, allocation_ids, amount, reason. Заменить — POST …/money/replacements (раздел «Деньги»), отменить — …/money/suggestions/{id}/undo.

Разбивка по заказам (group=orders — у JSON, PDF, Excel и отправки; нужно ещё право orders:read) — как «по договорам» в учётных системах: у каждого заказа своё сальдо на начало, строки, обороты и сальдо на конец, внизу — итог по контрагенту. Документ попадает в раздел своего заказа, оплата без платежа — в раздел заказа оплаченного счёта, операция «Денег» — по привязкам к документам заказов (разнесённая по счетам разных заказов делится, непривязанный остаток — «Без заказа»). Документы и оплаты поставщика и начальный остаток — в разделе «Без заказа». Сумма разделов всегда равна акту без разбивки; пустые разделы не возвращаются. 400 invalid_group — значение не orders.

GET …/counterparties/{id}/reconciliation?date_from=…&date_to=…&group=orders
→ { …, "group": "orders",
  "sections": [
    { "order": { "id": "…", "title": "Поставка октябрь" },
      "opening": { "balance": 0, "debit": 0, "credit": 0 }, "rows": [ … ],
      "turnover": { "debit": 4627200, "credit": 2500000 },
      "closing": { "balance": 2127200, "debit": 2127200, "credit": 0 } },
    { "order": null, … }
  ] }

Без разбивки group и sections — null. С разбивкой rows верхнего уровня — все строки подряд (операция, поделённая между заказами, — несколькими строками), итоги — те же.

Начальный остаток. Долг, накопленный до начала работы в Apiin, задаётся у контрагента один раз (право money:write, чтение — money:read) — дальше его учитывают все акты. Остаток — на начало дня date: документы и оплаты раньше этой даты в акт не входят. Если дата остатка внутри периода, остаток идёт строкой «Начальный остаток» в rows (и в обороты), а в файле — сноска «Документы и оплаты до … учтены в начальном остатке». В ответ карточки контрагента остаток не попадает, updated_at карточки не меняет.

GET …/counterparties/{id}/opening-balance     → 200 { "amount": …, "date": "…" } или null
PUT …/counterparties/{id}/opening-balance
{ "amount": -1500000, "date": "2026-01-01" }   → 200 { "amount": -1500000, "date": "2026-01-01" }
                                               (на 01.01.2026 вы должны контрагенту 15 000 ₽)

DELETE …/counterparties/{id}/opening-balance  → 204

Ошибки: 400 invalid_opening_balance — сумма по модулю больше 1 трлн ₽ или дата вне 2000–2100; 0 допустим — «на эту дату расчёты сведены», история до даты отсекается. Те же значения в PUT и DELETE без остатка ничего не меняют и не пишутся в журнал действий.

Отправка контрагенту. Акт за тот же период уходит письмом с выбранными вложениями или в переписку Apiin (раздел «Сообщения»), если контрагент подключён. Права: send:write + documents:read + money:read, для переписки — ещё messages:write. Письмо собирается в фоне: ответ 202 со статусом queued, итог (sent / failed с причиной) — в истории. В письмо сами добавляются период и итоговая фраза акта; ответ получателя придёт на адрес входа отправившего (Reply-To; через API-ключ — владельца организации). Лимиты — как у отправки документов. Письмо уходит с адреса Apiin, поэтому ссылки в теме и тексте не допускаются (400 links_not_allowed), а своя тема всегда начинается с «Акт сверки» (иначе получает префикс «Акт сверки: »). В истории хранится сводка (период, сальдо, адрес, вложения, статус), сам файл — нет; письмо, оставшееся «в очереди» дольше 10 минут (сервер перезапускался), показывается как failed — отправьте ещё раз.

POST …/counterparties/{id}/reconciliation/send
{
  "date_from": "2026-10-01", "date_to": "2026-10-31", "basis": "shipments",
  "group": "orders",                    // необязательно — с разбивкой по заказам
  "channel": "email",                   // или "messages"
  "to": "buh@romashka.ru",              // пусто — email из карточки контрагента
  "subject": "Акт сверки за октябрь",   // пусто — «Акт сверки за … — организация»
  "message": "Просим проверить…",       // не передан — стандартный текст, "" — без текста
  "pdf": true, "xlsx": true,            // хотя бы одно вложение
  "signatures": true,                   // подпись подписанта и печать организации в PDF
  "mirror": false                       // true — половина контрагента заполнена зеркально
}   → 202 (письмо) / 201 (переписка) — запись истории

GET …/counterparties/{id}/reconciliation/sends   → последние 50 отправок

Ошибки: 400 attachments_required (нет вложений), 400 email_required (нет адреса ни в запросе, ни в карточке), 400 invalid_channel, 400 invalid_email, 400 links_not_allowed, 400 messages_unavailable (контрагент не подключён к переписке), 403 link_not_active, а также ошибки периода и потолка PDF, как у самого акта.

В MCP: get_reconciliation, get_reconciliation_url, send_reconciliation, list_reconciliation_sends, get_opening_balance, set_opening_balance, clear_opening_balance, update_payment_date.

Язык документа

Поле language — ru или en. Не передано — берётся язык организации (PATCH …/organizations/{id}, поле language). Неподдерживаемый код — 400 с language_not_supported.

POST …/documents
{ "kind": "invoice", "counterparty_id": "…", "language": "en", "currency": "USD",
  "items": [ { "name": "Software development, hours", "quantity": 150, "price": 10000 } ] }
  • Переводятся системные надписи: заголовок, стороны, графы таблицы, итоги, подпись, футер, штамп электронной подписи. Даты печатаются как 17 August 2026 (месяц словом — 08/17 и 17/08 читаются по-разному в США и Европе), суммы как 1,234.56 USD (код валюты, а не символ: $ неоднозначен), сумма прописью — по-английски.
  • Введённые вами данные не переводятся: наименования позиций, примечание, тело договора, реквизиты и названия сторон печатаются как есть.
  • Счёт на иностранном языке печатается международной формой Invoice — с блоком платёжных реквизитов вместо банковской «шапки». НДС и платёжный QR при этом сохраняются, если применимы: рублёвый счёт на английском несёт и то, и другое.
  • Счёт-фактура и УПД печатаются по-русски всегда — это утверждённые формы (постановление 1137, письмо ФНС ММВ-20-3/96@): переведённая графа перестаёт быть этой формой.
  • В PATCH …/documents/{id} язык меняется в любой момент; пустая строка "" возвращает документ к языку организации. Копия и производный документ (акт по счёту) наследуют язык источника.
  • Язык применяется и к онлайн-версии документа по публичной ссылке, к DOCX, к имени файла и к письму контрагенту при отправке.

Валюта документа

Поле currency принимает коды ISO 4217: RUB, USD, EUR, CNY, GBP, CHF, AED, TRY, INR, KZT, BYN, AMD, AZN, GEL, UZS, KGS, HKD, SGD, THB. Не передано — валюта по умолчанию из организации (default_currency, изначально RUB). Неподдерживаемый код — 400 с currency_not_supported.

  • Валюта фиксируется при создании и не меняется у существующего документа: суммы позиций уже записаны в её минорных единицах. Копия и производный документ наследуют валюту источника.
  • Если язык документа не задан явно, не рублёвый счёт печатается на английском — так работало до появления настройки языка, и поведение сохранено. Явный language это правило перекрывает.
  • Валют без разменной единицы (JPY, KRW, VND) в справочнике нет: суммы хранятся в 1/100 единицы, и печатать иены с копейками было бы неверно.
  • Платёжный QR существует только для рублёвых платежей (ГОСТ Р 56042-2014), поэтому у валютного счёта его нет. Акты, договоры, счета-фактуры и УПД остаются на российских формах независимо от валюты расчётов.

Срок оплаты и напоминания

У счёта есть срок оплаты due_date (YYYY-MM-DD) и вычисляемый флаг overdue — срок прошёл, счёт не оплачен и не аннулирован. Просрочка не отдельный payment_status: статус остаётся unpaid|partial|paid, а overdue приходит рядом с ним в реестре, карточке, вебхуках и на публичной странице.

POST …/documents
{ "kind": "invoice", "counterparty_id": "…", "due_date": "2026-09-01", "items": […] }

GET …/documents?overdue=true       // только просроченные
POST …/documents/{id}/remind    // напомнить письмом прямо сейчас
  • Поле опущено — срок подставится из настройки организации default_payment_days (дней от даты выставления), если она задана. Правило работает во всех путях создания: обычный POST, копия, «создать на основе», акт по счёту и повторяющиеся счета. Явный null — счёт без срока даже при включённой настройке. Срок принимают только invoice и invoice_contract: по акту и отчёту не платят, а у счёта-фактуры форма 1137 строгая. Попытка задать due_date другому виду — 400 с кодом due_date_not_applicable; по такому документу и просрочка не считается.
  • PATCH …/documents/{id} меняет срок (null — снять). Смена срока обнуляет журнал напоминаний — у нового срока свой цикл.
  • Автонапоминания включаются у организации: reminders_enabled, reminder_days_before, reminder_days_after. Раз в сутки уходит до трёх писем на счёт — за N дней до срока, в день срока и через N дней после. Письмо идёт на contact_email контрагента; без него напоминание пропускается.
  • POST …/documents/{id}/remind — ручное напоминание (право send:write). Ошибки: 400 due_date_missing (срок не задан), 400 document_paid, 400 document_cancelled, 400 contact_email_missing, 400 document_not_payable (не счёт), 409 reminder_already_sent — повторно можно после смены срока.
  • Событие вебхука document.overdue отправляется один раз, вместе с первым напоминанием о просрочке.

В PDF и Word счёта печатается строка «Оплатить до ДД.ММ.ГГГГ».

Подписание документа простой электронной подписью

Акты, отчёты и договоры подписываются получателем прямо по публичной ссылке. Правовая рамка — соглашение об электронном документообороте (ст. 9 Федерального закона № 63-ФЗ): подписант соглашается с ним перед запросом кода. Счета-фактуры и УПД так подписать нельзя — для них нужна усиленная квалифицированная подпись через оператора ЭДО.

PATCH …/public-links/{id}
{ "allow_esign": true }        // владелец разрешает подписание по ссылке

POST /p/i/{slug}/sign/request  // публично, без авторизации
{ "name": "Иванов Иван Иванович", "email": "ivan@example.com", "accept_terms": true }
→ { "sent_to": "iv***@example.com", "expires_in_minutes": 15 }

POST /p/i/{slug}/sign/confirm
{ "email": "ivan@example.com", "code": "123456" }
→ { "signed_at": "2026-08-12T14:12:18Z", "signer_name": "Иванов Иван Иванович",
     "document_hash": "28a84806abd84f9a…" }
  • Код действует 15 минут, допускается 5 попыток ввода. В базе хранится только хеш кода.
  • При подтверждении фиксируются ФИО и email подписанта, дата и время, IP, браузер и SHA-256 PDF документа на этот момент: изменение документа после подписания делает подпись несоответствующей новому файлу.
  • Подписанный акт получает sign_status: "signed", уходит событие document.sign_status_changed, а в PDF появляется штамп с ФИО, датой и идентификатором подписи.
  • Подписи возвращаются в публичном представлении документа (GET /p/i/{slug}, поле signatures).
  • Коды ошибок: esign_not_allowed, kind_not_esignable, terms_not_accepted, signer_name_required, code_expired, bad_code, too_many_attempts, already_signed, document_cancelled.

Перенос данных из другого сервиса

Готового импорта файлов нет, но весь перенос делается через API — своим скриптом или нейросетью через MCP (там для этого есть инструмент import_invoice). Подробнее о вариантах переноса — на странице Перенос документов. Порядок для REST:

  1. Контрагенты. Проверьте, нет ли уже: GET …/counterparties?inn=…. Создайте POST …/counterparties — с "enrich_by_inn": true и inn реквизиты подтянутся из ЕГРЮЛ/ЕГРИП (лимит обогащения — 60 запросов в минуту на ключ, при 429 подождите минуту); физлица и контрагенты без ИНН — kind + name.
  2. Счета. POST …/documents со своим number и issue_date из старой системы; если в выгрузке нет позиций — одна позиция на сумму счёта. Повторный прогон: занятый номер вернёт 409 number_taken, найдите документ через ?number= — и обязательно сверьте его counterparty_id. Номер уникален в пределах серии и года, а серия по умолчанию общая на организацию, поэтому по номеру может найтись счёт другого контрагента: переиспользовать его нельзя, задайте новому счёту свой номер. Аннулированный документ номер освобождает.
  3. Оплаты. POST …/documents/{id}/mark-paid с { "paid_at": "ГГГГ-ММ-ДД" } — дата из старой системы, а не сегодняшняя. Это метка статуса: если нужна сверка сумм, переносите платежи через POST …/documents/{id}/payments.
  4. Акты. POST …/documents/{id}/create-act с number и issue_date акта, затем POST …/sign.
  5. Нумерация. Когда история перенесена — PUT …/numbering с next_seq для invoice и act, чтобы новые документы продолжили вашу серию (для контрагента со своим форматом — вместе с counterparty_id). Автоматически это не происходит: счётчик равен числу созданных документов, а не вашему последнему номеру.
  6. Откат. Ошибочный документ — DELETE …/documents/{id} (оплаченный/подписанный сначала mark-unpaid / unsign), контрагент без документов — DELETE …/counterparties/{id} (с документами — 409 counterparty_in_use).

Деньги: операции, расходы, выписки

Раздел «Деньги» — поступления и списания по счетам организации, расходные документы и чеки, загрузка банковских выписок с защитой от дублей и автопривязкой к счетам. Суммы — копейки. Права: money:read / money:write; всё, что меняет статус оплаты счетов, — ещё и payments:write.

  • GET /api/money/catalog — типы операций (key, title, directions), виды расходных документов и счетов.
  • …/money/accounts (+ PATCH …/{id}) — денежные счета с остатком (balance): банковские появляются из реквизитов сами, вручную — касса, карта; opening_balance/opening_date — остаток на начало учёта. …/money/categories — статьи (стартовый набор создаётся сам).
  • GET …/money/operations — фильтры date_from, date_to, account_id, direction, op_type, category_id, counterparty_id, review_status (new — «требует внимания»), unallocated=1 (поступления с непривязанным остатком), q, limit/offset; ответ {items, total}, у операции allocated и links. POST — вручную: { "kind": "in" | "out" | "transfer", "amount": …, … } (перевод — пара операций). PATCH — у операций из выписки меняется только классификация (operation_bank_fact), у перевода — только целиком (operation_transfer); сумма не ниже привязанной (operation_amount_below_allocated), тип привязанной операции — только тот, что оплачивает документ (operation_allocated_type). DELETE — только ручные (нужно ещё payments:write). Платёж, оплачивающий аннулированный счёт, не удаляется и не откатывается отменой загрузки (operation_linked_cancelled) — сначала восстановите счёт.
  • POST …/money/operations/{id}/allocations { "document_id": "…", "amount"?: … } — привязать поступление к счёту (права money:write + payments:write). Счёт оплачивают только оплата от покупателя, выручка эквайринга, «прочее» и «не разобрано» (оно становится оплатой от покупателя); перевод, займ, личные деньги и возврат — нет (400 operation_type_not_allocatable); DELETE …/money/allocations/{id}. Списание к расходному документу — …/expense-allocations.
  • …/money/expenses — расходные документы и кассовые чеки (продавец, сумма, НДС, ФН/ФД/ФП); при создании "pay": { "account_id": … } — «оплачено сразу» (появится списание). Вложения (фото чека, скан до 10 МБ, не больше 10 на запись — too_many_attachments; JPEG, PNG, GIF, WebP, PDF или офисный документ/ZIP — тип определяется по содержимому, иначе bad_file_type) — POST …/money/{operations|expenses}/{id}/attachments (multipart).
  • GET …/money/summary, GET …/money/analytics?group_by=month|category|counterparty, GET …/money/operations/export.csv|xlsx — без переводов между своими счетами.

Загрузка выписки

POST …/money/imports/preview      // multipart, поле file — выписка «в 1С» (kl_to_1c.txt)
→ { "preview_id": "…", "already_loaded_at": null,
    "accounts": [{ "account_number": "4070…", "bank_closing": …, "our_closing": …, "diff": 0 }],
    "rows": [{ "row_id": 0, "status": "new", "op_type": "customer_payment",
               "match": { "rank": "exact", "auto": true, "documents": [{ "number": "17", … }] } }, …],
    "missing": [ … ], "summary": { "new": 5, "existing": 0, "auto_allocated": 1 } }

POST …/money/imports/apply
{ "preview_id": "…", "decisions": { "3": { "action": "skip" }, "4": { "op_type": "tax" } },
  "delete_missing": [], "set_opening": true }
  • Формат — только стандарт обмена 1С с клиент-банком (1CClientBankExchange, «Экспорт в 1С»): кодировки Windows, DOS и UTF-8, несколько счетов в одном файле, остатки по счёту (и посуточные секции, как у Сбера). PDF, Excel, Word, CSV банка и ED211 Банка России не принимаются — 400 bad_statement называет формат и подсказывает, где взять выгрузку в 1С. Файл до 10 МБ. Предпросмотр хранится 30 минут.
  • Покупка по карте (деньги ушли на транзитный счёт банка 30232…) получает продавца из назначения платежа, а не банк; перевод себе (ИНН плательщика и получателя совпадают) — тип «Личные деньги владельца»; выплаты эквайринга «по реестру операций» — «Выручка через эквайринг».
  • Дублей нет: строка сравнивается с уже загруженными по отпечатку (счёт, направление, дата, сумма, номер, ИНН) с учётом кратности одинаковых платежей одного дня. Статусы строк: new, existing, changed (банк поправил сумму или дату), manual_twin (похоже на операцию, введённую вручную, — склеивается). missing — загружали раньше, а в новой выписке нет.
  • Автопривязка по номеру счёта в назначении и ИНН плательщика: точные совпадения привязываются сразу (режим организации exact), вероятные попадают в предложения «?» — GET …/money/suggestions, POST …/money/suggestions/{id}/accept|reject (отклонённая пара больше не предлагается; предложение гаснет само, если платёж распределён или счёт оплачен иначе). Ответ — 204; права: просмотр — money:read, решение — money:write, оба — с payments:write. Подтверждение замены ручной отметки снимает в нужном объёме отметки «Оплачен» и перенесённые старые оплаты (legacy) — оплаты суммой (наличные) остаются.
  • Одни и те же деньги дважды: счёт оплачен без платежа (вручную, отметкой «Оплачен»), а в выписке пришёл платёж на ту же сумму. Импорт предлагает замену — ранг replace_manual с replace_allocation_ids (какие оплаты снимутся). Правило строгое: тот же ИНН или номер счёта в назначении, платёж не позже даты ручной оплаты + 3 дня, сумма совпадает точно — с одной оплатой, со всеми оплатами счёта или нескольких счетов сразу (тогда предложение на каждый счёт). Если обычное совпадение по номеру счёта забирает платёж не целиком, а ручные оплаты совпадают по сумме точно, предлагается замена, а не автопривязка. Подтверждение снимает ровно эти оплаты и привязывает платёж на их сумму (409 suggestion_outdated — оплаты изменились; 400 operation_allocated — платёж уже разнесён). Отмена — POST …/money/suggestions/{id}/undo: привязка снимается, оплаты возвращаются с прежними id и авторством, предложение снова ждёт решения (409 undo_overpaid — счёт успели оплатить другими платежами). Отмена загрузки выписки сама возвращает заменённые оплаты. Из акта сверки (пары replacements[]) — POST …/money/replacements {"operation_id", "document_id", "allocation_ids", "amount"} (amount необязателен — сумма, которую видел человек; разошлась — 409 suggestion_outdated) → 201 {"suggestion_id", "document_id", "amount"}. Ошибки: 409 replacement_exists (пара уже заменена — сначала отмените), 404 operation_not_found; у отмены — 400 undo_unavailable, 400 document_cancelled (счёт аннулирован). «Снять» привязку, созданную заменой (DELETE …/allocations/{id}), — это отмена замены. В журнале действий — document.payment_replaced и document.payment_replace_undone.
  • Тип операции угадывается: правило организации (…/money/rules) → тип у контрагента → признаки (КБК — налог, банковский ордер — комиссия, свой ИНН — свои деньги). Нераспознанные — review_status: "new".
  • GET …/money/imports — история; POST …/money/imports/{id}/undo — отмена в течение 30 дней (операции снимаются, исправленные выпиской суммы и даты возвращаются, статусы счетов пересчитываются). Если исправленный платёж уже привязан на большую сумму — 400 batch_allocated_over. Предпросмотр применяется один раз (повтор — preview_expired, параллельный — 409).

Импорт выписки (прежний, устаревший)

Прежний двухшаговый импорт …/bank-import/preview|apply работает для совместимости, но не хранит операции и не защищает от дублей. Используйте …/money/imports.

POST …/bank-import/preview        // multipart, поле file — файл выписки
→ {
  "account": "40702810900000000001",
  "total": 4, "incoming": 3, "matched": 2,
  "rows": [
    { "index": 0, "number": "125", "date": "12.08.2026", "amount": 4500000,
      "purpose": "Оплата по счету № 1 от 12.08.2026",
      "payer_name": "ООО «Ромашка»", "payer_inn": "7707083893",
      "document_id": "<uuid>", "document_number": "1",
      "confidence": "exact", "outstanding": 4500000 }, …
  ]
}

POST …/bank-import/apply
{ "items": [ { "document_id": "<uuid>", "amount": 4500000,
              "paid_at": "2026-08-12", "note": "Оплата по счету № 1" } ] }
→ { "applied": 1, "failed": 0, "results": [ { "document_id": "…", "status": "ok" } ] }
  • Отбираются только входящие платежи — те, где счёт получателя совпадает с расчётным счётом организации (учитываются и дополнительные счета из справочника). Если расчётный счёт не заполнен, приходит 400 account_missing.
  • confidence показывает, как подобран документ: exact — номер счёта найден в назначении платежа, likely — совпали ИНН плательщика и сумма остатка, weak — совпала только сумма и кандидат единственный. Неоднозначные строки остаются без сопоставления: сервер не угадывает.
  • apply обрабатывает строки независимо — ошибка по одному документу не отменяет остальные; в results у каждой строки свой status (ok, not_found, bad_amount, error). За один вызов принимается до 500 платежей.
  • Записанные платежи ничем не отличаются от внесённых вручную: статус оплаты пересчитывается, событие document.payment_status_changed уходит в вебхук.
  • Файл до 4 МиБ, кодировка windows-1251 или UTF-8 — определяется автоматически.

Сообщения: переписка, обсуждения, обмен документами

Раздел «Сообщения» — беседы организации: переписка со связанными организациями-контрагентами (direct), обсуждения документов и внутренние темы команды (internal). Права: messages:read / messages:write; приложить документ — ещё documents:read. Подпись автора — снимок «Имя · должность · организация» (должность — своя в каждой организации: PUT …/members/{id}/position); у ключа — «API-ключ · организация», у MCP — «Нейросеть (MCP) от имени …».

  • GET …/threads — беседы (kind через запятую, unread=1, archived=1, q, limit/offset): последнее сообщение, собеседник (other_party), непрочитанное для пользователя; GET …/threads/unread — счётчики по видам.
  • POST …/threads { "document_id"?: …, "subject"?: …, "body"?: … } — внутреннее обсуждение (одно на документ — повтор вернёт существующее) или тема команды; PATCH …/threads/{id} — тема, архив у себя.
  • GET …/threads/{id}/messages (курсор before + before_id), POST { "body": …, "file_ids": […], "document_ids": […] } — до 5000 символов и 10 вложений; доверенность не прикладывается (kind_not_shareable), в переписку с организацией — только документы её контрагента (document_other_counterparty). Файлы загружаются заранее: POST …/message-files (multipart, до 10 МБ, тип по содержимому). Лимиты на автора: 60 сообщений в минуту, 100 файлов в час (429).
  • PATCH …/threads/{id}/messages/{mid} — правка своего (от своей стороны беседы) в течение 15 минут; DELETE — мягкое удаление: в ленте «Сообщение удалено пользователем …», текст и вложения хранятся не меньше 3 лет и людям не отдаются. POST …/threads/{id}/read — прочитано.
  • Связь организаций: POST …/links { "counterparty_id": …, "email"?: …, "message"?: … } — если с ИНН контрагента в Apiin ровно одна организация (или связь уже есть), её администраторам уходит запрос; ответ status: requested (и повтор — без нового уведомления), activated (встречный запрос — открывает только владелец или администратор в кабинете, иначе awaiting_admin), already_active. Иначе — письмо-приглашение на email (invited; отправляют владелец и администраторы, до 20 в сутки). Принять — POST /api/link-invites/{token}/accept: только адресат (email входа совпадает с приглашением или ИНН организации — с ИНН контрагента), иначе invite_other_recipient. Название и ИНН второй стороны скрыты до её согласия; если отклонили ваш запрос, повторить можно через сутки (link_declined_recently); не больше 30 приглашений в час от автора, уведомление о запросе — не чаще 3 раз в сутки на пару организаций. Решение — POST …/links/{id}/accept|decline|block|unblock (владелец или администратор в кабинете; OAuth-приложение и API-ключ — 403 link_admin_only), declined_recently в ответе — ваш запрос отклонили меньше суток назад. Активная связь открывает переписку; писать при неактивной — 403 link_not_active. Статус для карточки — GET …/counterparties/{id}/link.
  • Обмен документами: документ во вложении переписки вторая сторона видит (сумма, статус оплаты) и открывает — GET …/message-files/{id}/document.pdf; POST …/message-files/{id}/paid — «Мы оплатили» (системное сообщение и уведомление владельцу, статус меняет владелец). Оба — только пока связь активна.
  • События документа (оплата, подпись, аннулирование, отправка, правки, запрос доступа) появляются в его беседах системными сообщениями; вебхук message.created.
  • GET …/counterparties/{id}/timeline (курсор before + before_id) — история взаимодействий: документы, оплаты, деньги (при money:read), переписка (при messages:read).
  • Письма-дайджесты «У вас новые события» — не чаще раза в час, только о непрочитанном; выключить — PATCH /api/me/profile { "notify_email_enabled": false }.

Каталог: товары, рубрики и витрина

Каталог питает позиции документов и публичный каталог. Цены — в копейках, как везде в API.

  • GET|POST /api/organizations/{org_id}/products — список и создание карточек. Поля: kind (product|service), name, sku, unit, price, vat_rate, description, attrs (характеристики), attribute_set_id, category_id (рубрика) и videos. Список принимает ?archived=1 и ?category_id=.
  • GET|POST /api/organizations/{org_id}/product-categories — рубрики каталога (плоский список, у товара одна). PATCH переименовывает, PUT …/order задаёт порядок. Имя уникально в организации: повтор — 409.
  • POST /api/organizations/{org_id}/products/bulk — массовая операция над списком ids (до 500): price (режимы set|inc|dec|percent), category, kind, unit, vat_rate, description, attr, attribute_set, archive, publish, storefront, copy, delete. Ответ — {updated, skipped[]}: частичный успех это норма, причина по каждой пропущенной позиции приходит текстом.
  • GET|POST|PATCH|DELETE /api/organizations/{org_id}/storefront — витрина (одна на организацию): заголовок, описание, show_all, category_ids, product_ids и адрес публичного каталога. При выключении и удалении можно погасить ссылки её товаров: ?hide_product_links=1.

Товар витрины публикуется принудительно. Попав в каталог (напрямую, через рубрику или через «показывать все»), он получает публичную ссылку автоматически, и отключить или отозвать её нельзя, пока он там: 409 product_on_storefront. Сначала уберите товар из витрины.

videos принимает ссылки на YouTube, RuTube и VK Видео (до 5) строкой или объектом {url, title}. Сервер распознаёт площадку и возвращает готовый embed_url для плеера; неизвестный адрес — 400.

Теги документов

Теги — цветные метки уровня организации: помечайте документы, созданные вашей интеграцией, и отличайте их от ручных. Справочник: GET/POST …/tags, PATCH/DELETE …/tags/{id} (имя ≤50 символов уникально, цвет #RRGGBB). Присвоение — декларативно: PUT …/documents/{id}/tags с { "tag_ids": […] } (полная замена набора; пустой массив снимает все).

// Пометить автоматический счёт прямо при создании:
POST …/documents
{ "kind": "invoice", "counterparty_id": "…", "tag_ids": ["<uuid тега>"], "items": […] }

// Фильтры списка:
GET …/documents?tag_ids=<uuid>,<uuid>   // с любым из тегов
GET …/documents?untagged=1               // только без тегов

«Создать подобный» и «создать на основе» наследуют теги источника. В payload каждого события вебхука документ несёт tag_ids — маршрутизируйте события по своим меткам. Все связи организации одним запросом: GET …/document-tags.

Предложения правок

Получатель документа может прислать предложение изменений, если владелец включил разрешение на публичной ссылке: PATCH …/public-links/{id} с { "allow_proposals": true }. Предложение — это неизменяемый снапшот содержания плюс база сравнения, поэтому подсветка изменений не «плывёт», даже если документ позже правили. Изменить можно ТОЛЬКО содержание (позиции, дата, описание, основание, примечание, текст договора) — реквизиты сторон, номер и оформление недоступны.

  • Лента предложений документа: GET …/documents/{id}/proposals — каждое несёт готовый diff (список изменений «было → стало», счётчики позиций, итог до и после) и summary;
  • Принять: POST …/proposals/{pid}/accept — снапшот применяется к документу целиком, суммы пересчитывает сервер. Правка и решение — одной транзакцией: если правка не прошла (409 number_taken при переносе даты в другой год, 400 discount_not_applicable), предложение остаётся ожидающим. Период и вознаграждение отчёта агента, настройки страницы договора и связь позиций с каталогом (их нет в редакторе) принятие не меняет; очищенное в предложении поле очищается;
  • Отклонить: POST …/proposals/{pid}/decline (можно с причиной в message);
  • Встречный вариант: POST …/proposals/{pid}/counter с payload — исходное предложение помечается «заменено», а решение переходит к другой стороне (сравниваются два последних варианта). Ответить на уже решённое предложение нельзя — 400 proposal_decided.

Флаг outdated у ожидающего предложения означает, что содержание документа (то, что меняет предложение, а у УПД/СФ ещё режим «цены включают НДС») отличается от состояния, с которого начались переговоры, — принятие затрёт эти изменения, стоит пересмотреть сравнение. Оплата, подпись, закрытие и номер предложение не «устаревают»; у решённых — false. События вебхука: document.proposal_submitted и document.proposal_accepted. Решение принимает пользователь кабинета: API-ключ читает предложения, но не решает за человека.

Вебхуки

Чтобы узнавать об оплате и подписи без опроса, настройте вебхук (право webhooks:write, роль admin или owner). Приёмников может быть до 10 на организацию — например, отдельные адреса для прода, тестового стенда и CRM; у каждого своё имя, подписка и секрет подписи:

POST https://api.apiin.ru/api/organizations/{org_id}/webhooks
Authorization: Bearer apiin_live_xxxxxxxx
Content-Type: application/json

{
  "name": "Прод",
  "url": "https://example.com/apiin-webhook",
  "events": ["document.payment_status_changed", "document.sign_status_changed"]
}

Управление приёмниками:

  • GET …/webhooks — список (id, имя, подписка, статус; БЕЗ секретов);
  • POST …/webhooks — создать (409 webhook_limit_reached — лимит 10);
  • PATCH …/webhooks/{id} — изменить переданные поля (имя, url, события, enabled для паузы);
  • POST …/webhooks/{id}/rotate — перевыпустить секрет (ответ содержит новый);
  • GET …/webhooks/{id}/secret — секрет приёмника отдельным запросом;
  • POST …/webhooks/{id}/test — тестовое событие webhook.test;
  • DELETE …/webhooks/{id} — удалить приёмник;
  • GET …/webhooks/{id}/deliveries?limit= — журнал ОДНОГО приёмника;
  • GET …/webhooks/deliveries?limit=&webhook_id= — сводный журнал организации; в каждой записи есть webhook_id, поэтому видно, чей это отказ.

Прежние singleton-пути GET/PUT/DELETE …/webhook, …/webhook/test и …/webhook/deliveries продолжают работать и относятся к первому вебхуку организации — уже настроенные интеграции менять не нужно.

События (15):

  • document.created — создан документ;
  • document.sent — документ отправлен на email;
  • document.payment_status_changed — статус оплаты (unpaid|partial|paid);
  • document.sign_status_changed — статус подписи закрывающего документа (акт, УПД, отчёт);
  • document.closing_status_changed — у счёта сменился статус закрытия (not_closed | partially_closed | closed): привязали или сняли акт/УПД/отчёт, изменили итог закрывающего документа или самого счёта, аннулировали, восстановили или удалили закрывающий документ; в теле — closed_amount и closing_status. Событие — ровно одно на каждый переход (и при параллельных изменениях), после события-причины (document.created, document.cancelled и т.п.). Тогда же двигается updated_at счёта, так что ?updated_since= его увидит (меняется и сумма без смены статуса — тогда только updated_at);
  • document.overdue — срок оплаты прошёл, счёт не оплачен;
  • document.cancelled — документ аннулирован;
  • document.uncancelled — документ восстановлен после аннулирования;
  • document.proposal_submitted — получатель прислал правки;
  • document.proposal_accepted — правки приняты, документ изменён;
  • recurring.generated — сгенерирован повторяющийся счёт;
  • access.requested — запрошен доступ к документу;
  • access.granted — доступ к документу выдан;
  • document.poa_status_changed — доверенность отмечена использованной, отозвана или снова выдана;
  • message.created — новое сообщение в беседе организации (раздел «Сообщения»): в теле thread (id, kind, subject, document_id) и message (author_label, author_kind, body, число вложений) вместо document.

Каждое событие — POST на ваш адрес. Пример тела:

{
  "event": "document.payment_status_changed",
  "occurred_at": "2026-08-11T10:32:07Z",
  "organization_id": "<uuid>",
  "document": {
    "id": "<uuid>",
    "kind": "invoice",
    "number": "СЧ-42",
    "counterparty_id": "<uuid>",
    "total": 4500000,
    "payment_status": "paid",
    "paid_at": "2026-08-11T10:32:07Z",
    "due_date": "2026-08-20",
    "overdue": false,
    "sign_status": "not_signed",
    "cancelled_at": null,
    "tag_ids": ["<uuid тега>"],
    "poa_status": null,
    "valid_until": null
  }
}

Состав document в payload фиксирован: id, kind, number, counterparty_id, total, payment_status, paid_at, due_date, overdue, sign_status, cancelled_at, tag_ids, poa_status и valid_until (у доверенности — статус «на сегодня» issued|used|revoked|expired и срок действия, у прочих видов — null). Полей достаточно для сверки оплат и долгов без дополнительного запроса документа.

Заголовки запроса:

  • X-Apiin-Event — имя события;
  • X-Apiin-Delivery — UUID доставки (используйте для идемпотентности приёмника);
  • X-Apiin-Signature: sha256=<hex> — HMAC-SHA256 над сырыми байтами тела запроса (проверяйте подпись до парсинга JSON — по исходной строке, а не по пересериализованному объекту). Секрет подписи возвращается при создании (POST …/webhooks), при перевыпуске (POST …/webhooks/{id}/rotate) и по отдельному запросу GET …/webhooks/{id}/secret — в списках его нет, чтобы он не уезжал по сети без необходимости. Проверяйте подпись, прежде чем доверять телу.

Проверка подписи

HMAC-SHA256 считается от сырых байтов тела запроса ключом-секретом вебхука; результат — hex в нижнем регистре, а заголовок содержит его с префиксом: X-Apiin-Signature: sha256=<hex>. Сравнивайте значения константным по времени сравнением и только потом парсите JSON.

// Node.js (Express): важно получить именно сырое тело
import express from 'express';
import crypto from 'node:crypto';

app.post('/apiin-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const received = String(req.get('X-Apiin-Signature') || '');
  const expected =
    'sha256=' + crypto.createHmac('sha256', process.env.APIIN_WEBHOOK_SECRET)
      .update(req.body)             // Buffer с исходными байтами, НЕ JSON.stringify
      .digest('hex');

  const ok = received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!ok) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString('utf8'));
  // …обработка; ответить 2xx в течение 10 секунд
  res.sendStatus(200);
});
# Python (FastAPI)
import hmac, hashlib
from fastapi import Request, HTTPException

@app.post("/apiin-webhook")
async def apiin_webhook(request: Request):
    raw = await request.body()                     # сырые байты
    expected = "sha256=" + hmac.new(SECRET.encode(), raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(request.headers.get("X-Apiin-Signature", ""), expected):
        raise HTTPException(status_code=401)
    event = json.loads(raw)
    return {"ok": True}

Типичная ошибка — считать подпись по пересериализованному объекту: порядок ключей и пробелы изменятся, и подпись не совпадёт. Секрет берётся из ответа создания или перевыпуска либо запрашивается отдельно: GET …/webhooks/{id}/secret. Если приёмников несколько, у каждого свой секрет — проверяйте подпись тем, который соответствует адресу.

Доставка считается успешной при ответе 2xx за 10 секунд. Иначе — повторы с увеличивающейся паузой (до 6 попыток за ~9 часов), после чего доставка помечается несостоявшейся. Журнал: GET …/webhooks/{id}/deliveries (или сводный GET …/webhooks/deliveries); тестовое событие: POST …/webhooks/{id}/test. Событие уходит на КАЖДЫЙ включённый приёмник, подписанный на него, — доставки независимы, отказ одного не влияет на остальные.

Адрес вебхука должен быть публичным https на порту 443. Локальные и приватные адреса отклоняются — для локальной отладки используйте туннель (например, ngrok).

Списки: пагинация и синхронизация

Списки документов и контрагентов принимают необязательные query-параметры. Без них возвращается весь список (как раньше). Для интеграций:

  • ?limit= и ?offset= — пагинация (limit от 1 до 500).
  • ?updated_since= — только изменённые с указанного момента (формат RFC3339, например 2026-07-17T10:00:00Z) — для инкрементальной синхронизации.
  • ?inn= (только контрагенты) — точный поиск по ИНН: удобно проверить, есть ли уже такой контрагент, перед созданием.
  • ?sort=recent (только контрагенты) — сначала те, с кем был последний документ (по дате документа); контрагенты без документов — в конце. По умолчанию (sort=created) — новые карточки сверху.

Фильтры и сортировка документов

GET /api/organizations/{id}/documents дополнительно принимает:

  • ?status= — unpaid, partial, paid, overdue, unsigned, signed, cancelled. Оплата проверяется только у счетов и счетов-договоров, подпись — у закрывающих документов (акт, УПД, отчёт); аннулированные документы попадают лишь под cancelled.
  • ?date_from= и ?date_to= — период по дате документа (ГГГГ-ММ-ДД, границы включительно).
  • ?sort= — number, kind, issue_date, total, payment, sign, created_at (по умолчанию created_at) и ?order=asc|desc.
  • ?act=without — счета и счета-договоры без действующего закрывающего документа (акта, УПД или отчёта; аннулированный не закрывает) — что осталось закрыть, ?act=with — с закрывающими (закрытые полностью или частично), ?act=partial — закрытые частично, ?act=open — что осталось закрыть (не закрытые и закрытые частично). Сочетание act=without|partial|open с kind=act или статусами подписи даёт 400: такой набор заведомо пуст.
  • ?kind=, ?counterparty_id=, ?order_id=, ?number=, ?tag_ids= (через запятую) или ?untagged=1, ?overdue=1.
  • ?created_by= — документы, созданные участником организации (id из GET …/members), в том числе через его MCP-подключение; ?created_via= — путь создания: user (кабинет), oauth (MCP), api_key (API-ключ), system (повторяющийся счёт).

Ответ несёт заголовок X-Total-Count — сколько всего документов подходит под фильтры до пагинации. Неизвестное значение status, sort или order — это 400, а не молчаливая отдача всего списка.

Те же параметры принимают выгрузки documents/export.xlsx и export.csv.

Параметр выгрузки detail выбирает вид файла: documents (по умолчанию, documents_export.*) — строка на документ: №, дата, вид, контрагент, ИНН контрагента, основание, сумма (у доверенности пусто), НДС (= vat_amount_effective, как в печати), валюта, статус оплаты (у счетов), оплатить до, просрочен, статус подписи (у закрывающих документов), аннулирован; items (documents_items.*) — строка на позицию: те же реквизиты документа, наименование, кол-во, ед., цена, скидка (%), сумма, ставка НДС, «НДС в цене», НДС, всего, валюта, статус оплаты, аннулирован. «Цена» и «Сумма» — как в строке документа; «НДС в цене» говорит, с налогом они (да — счета, акты, УПД и счёт-фактура с prices_include_vat) или без (нет — УПД и счёт-фактура с налогом сверху); у строки без ставки ячейка пуста. «Всего» — всегда с налогом: по строкам документа оно складывается в его итог. При ручной сумме НДС документа налог по строкам не выводится — он задан на документ. Документ без позиций — одна строка с пустыми графами позиции; у доверенности денежные графы пусты. Другое значение detail — 400 bad_detail.

Строк в файле — не больше 20 000 (у реестра — документов, у позиций — строк), иначе 400 export_too_large: сузьте период или фильтры. Файл собирается в общей очереди тяжёлых отчётов, как акт сверки: занятый сервер отвечает 503 server_busy с Retry-After; больше 120 выгрузок за 10 минут на организацию (реестр, позиции и «Деньги» вместе, отдельно от акта сверки) — 429. Ошибка в параметрах (400) лимит не тратит.

В Excel (.xlsx) суммы и количество — числа, даты — даты, ИНН и номера — текст. CSV — для 1С и учётных программ: суммы 126540,00, даты ДД.ММ.ГГГГ, текст как есть (начинающийся с = + - @ — с апострофом, чтобы не стал формулой). Для Excel берите .xlsx: открывая CSV, Excel сам угадывает типы и показывает ИНН числом без ведущего нуля.

Изменение поведения (30.10.2026): в реестре суммы стали числами (в CSV — 126540,00 вместо 126 540,00), колонка «Сумма, ₽» — «Сумма», после «Контрагента» добавлены «ИНН контрагента» и «Основание», после суммы — «НДС» и «Валюта»; «Оплатить до» — только дата, просрочка — колонкой «Просрочен», в конце — «Аннулирован»; статус оплаты пуст у неплатёжных видов, статус подписи — у незакрывающих (как в кабинете); у доверенности сумма пуста (была 0,00); текст CSV, начинающийся с = + - @, получает апостроф (раньше в реестре — нет). Интеграции, читающие колонки по номеру, нужно поправить.

Изменение поведения (30.10.2026): выгрузка операций money/operations/export.csv пишет сумму как -1234,56 (было -1234.56 — Excel с русскими настройками читал её текстом, а 12.05 — датой); в .xlsx дата операции — дата Excel, шапка закреплена, есть автофильтр. Больше 20 000 операций — 400 export_too_large (раньше файл молча обрезался на 10 000, и сумма по нему была неверной). Даты в Excel журнала доверенностей и акта сверки — тоже даты, а не текст.

В списке у каждого счёта приходят поля последнего действующего закрывающего документа — act_id, act_kind (act | upd | report), act_number, act_issue_date, act_sign_status (или null, если счёт не закрыт): по ним видно, что ещё не закрыто, без отдельного запроса связок. Имена полей — исторические. У счёта в любом ответе — closed_amount (сколько закрыто закрывающими документами, не больше итога) и closing_status: not_closed | partially_closed | closed; у прочих видов оба null. В сводке — invoices_partially_closed и unclosed_amount (сколько осталось закрыть).

Каждый документ несёт автора: created_by (id участника или null) и created_via (user | oauth | api_key | system). У документов, созданных до 30.09.2026, оба поля — null.

Журнал действий

Владелец и администраторы видят в кабинете (Команда → Журнал), кто и когда создавал, менял и удалял документы, отмечал оплату и подпись, отправлял документы, правил контрагентов, реквизиты, банковские счета, роли и API-ключи. Действия через API-ключ записываются как «API-ключ «название»», через MCP — на участника, подключившего MCP. Лента доступна только из кабинета: GET /api/organizations/{id}/activity с фильтрами actor, actor_kind, entity, entity_id, from/to и курсором cursor из поля next_cursor предыдущей страницы.

Сводка по документам

GET /api/organizations/{id}/documents/stats считает агрегаты по тем же фильтрам, что и список, — без выкачивания самих документов. Добавьте ?by_counterparty=0, если разрез по контрагентам не нужен.

{
  "summary": {
    "counterparty_id": null,
    "documents": 1420, "invoices": 807, "acts": 613,
    "billed": 3257061843, "paid": 3072000000, "debt": 185061843,
    "overdue_count": 12, "overdue_total": 98000000,
    "unpaid_count": 9,  "unpaid_total": 87061843,
    "partial_count": 1, "partial_total": 11000000,
    "paid_count": 797,  "paid_total": 3072000000,
    "acts_unsigned": 0, "invoices_without_act": 192,
    "invoices_partially_closed": 3, "unclosed_amount": 54000000,
    "first_issue_date": "2016-05-06", "last_issue_date": "2026-09-03",
    "last_created_at": "2026-09-03T08:12:41Z"
  },
  "by_counterparty": [ { "counterparty_id": "…", "documents": 24, "debt": 18000000, "…": "…" } ]
}

Деньги — копейки и считаются только по счетам: акт несёт ту же сумму, что и счёт, и сложение удвоило бы оборот. paid — полностью оплаченные целиком плюс суммы платежей по частично оплаченным; debt = billed − paid. Аннулированные документы в денежные агрегаты не входят.

Идемпотентность (защита от дублей)

Создающие запросы POST /documents и POST /counterparties принимают заголовок Idempotency-Key: <уникальная строка>. Первый запрос создаёт сущность и запоминает результат на 24 часа; повтор с тем же ключом вернёт ту же сущность (статус 200), а не дубль с новым номером. Если предыдущий запрос ещё выполняется — повтор получит 409. Используйте это при ретраях после сетевого таймаута: сгенерируйте ключ один раз на операцию и отправляйте его при каждой попытке.

Ошибки

Ошибки возвращаются с соответствующим HTTP-статусом (400 — неверные данные, 401 — нет/недействительный ключ, 403 — не хватает прав, 404 — не найдено или чужая организация, 409 — конфликт, 429 — превышен лимит, 503 — сервер временно занят) и телом { "error": "<описание>", "code": "<машинный код>" }. Поле code — стабильный идентификатор (например rate_limited, not_found, document_protected); в коде опирайтесь на него, а не на текст error. При 429 приходит заголовок Retry-After (секунды до повтора) и X-RateLimit-*.

Тяжёлые отчёты — PDF и Excel акта сверки и журнала доверенностей, отправка акта сверки — формируются в общей очереди сервиса (по два одновременно). Если очередь не освободилась за 15 секунд, придёт 503 server_busy с Retry-After: 30 — повторите запрос позже. На организацию — не больше 60 таких отчётов за 10 минут (429 rate_limited).

Создайте первый ключ в кабинете. Для управления документами из нейросети — раздел MCP для нейросетей.