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);public_links:write— публичные ссылки;send:write— отправка на email;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. Пустые поля на карточке не выводятся, а по
дате регистрации там показан возраст организации.
Реестр операторов персональных данных
Если организация внесена в реестр операторов ПДн Роскомнадзора, номер и дату можно показать
контрагентам. Поля организации: 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). Поле manual_vat_amount (копейки, опционально)
переопределяет отображаемую сумму НДС при ОСНО, не меняя total; null возвращает авторасчёт. У счёта-фактуры НДС считается «сверху»
(цены без налога) — своя модель, manual_vat_amount не действует.
Позиция принимает необязательный product_id — товар каталога, из которого она
взята. Это только пометка: наименование, цена, единица и ставка остаются снимком и по ссылке не перечитываются (документ — юридическая запись), а кабинет по ней показывает
«товар в каталоге изменился, обновить позицию?». Чужой товар — 400.
Позиция принимает необязательный discount_percent — скидку в процентах
(0..100, можно дробную: 7.5). Она уменьшает сумму строки,
цена печатается прежней, а рядом появляется колонка «Скидка»; НДС считается уже от суммы
со скидкой. Скидка допустима только в invoice, invoice_contract, act и report: в счёте-фактуре и УПД графы «скидка» нет
(там гр.5 = гр.3 × гр.4), поэтому скидка в них — 400 discount_not_applicable, а указывать нужно цену уже со скидкой. При выводе
счёта-фактуры или УПД из счёта со скидкой она сворачивается в цену автоматически.
Массовые действия над документами — 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 (универсальный передаточный
документ, статус 1 или 2 в поле upd_status), power_of_attorney (доверенность М-2/М-2а или свободная, см. ниже).
В ответе — документ с id, номером и рассчитанными суммами
(subtotal, vat_amount, total).
Доверенность (М-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(сузьте период).
Справочник сотрудников
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, счёте-фактуре и УПД — поле организации chief_accountant_name.
Собственная нумерация
Если у вас своя сквозная нумерация, передайте 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,
"signature_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.with_signatures: falseубирает подпись и печать разом (общий выключатель),hide_*— точечно.tag_idsпроставляются при создании. Все теги должны принадлежать организации, иначе400с кодомtag_not_found(документ не создаётся) — берите UUID изGET …/tags. Изменить набор позже:PUT …/documents/{id}/tags.- Опущенные
logo_asset_id/stamp_asset_id/signature_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 и публичная ссылка
- PDF:
GET …/documents/{id}/pdf(добавьте?download=1для вложения); - Word:
GET …/documents/{id}/docx; - Публичная ссылка на счёт:
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; - Оплата с выбором платежа:
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. Связка закрывается сама, когда счёт оплачен, а акт подписан; - Аннулировать документ:
POST …/documents/{id}/cancel(идемпотентно) — документ получаетcancelled_at, в PDF появляется пометка «АННУЛИРОВАН», платёжный QR скрывается, а оплата/подпись/отправка и правка (PATCH, в том числе массовая) отвечают400с кодомdocument_cancelled. Исключение — перенос в заказ (PATCHтолько сorder_id): заказ лишь группирует реестр. Теги меняются как обычно. Чтобы исправить документ, восстановите его или создайте подобный (…/copy). Восстановление —POST …/uncancel. События вебхука:document.cancelled/document.uncancelled. Нюанс: если ссылка онлайн-оплаты была создана ДО аннулирования и покупатель по ней заплатил, платёж фиксируется (деньги поступили — запись нужна для сверки); владелец видит противоречие и решает — вернуть платёж или восстановить документ.
Язык документа
Поле 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:
- Контрагенты. Проверьте, нет ли уже:
GET …/counterparties?inn=…. СоздайтеPOST …/counterparties— с"enrich_by_inn": trueиinnреквизиты подтянутся из ЕГРЮЛ/ЕГРИП (лимит обогащения — 60 запросов в минуту на ключ, при429подождите минуту); физлица и контрагенты без ИНН —kind+name. - Счета.
POST …/documentsсо своимnumberиissue_dateиз старой системы; если в выгрузке нет позиций — одна позиция на сумму счёта. Повторный прогон: занятый номер вернёт409 number_taken, найдите документ через?number=— и обязательно сверьте егоcounterparty_id. Номер уникален в пределах серии и года, а серия по умолчанию общая на организацию, поэтому по номеру может найтись счёт другого контрагента: переиспользовать его нельзя, задайте новому счёту свой номер. Аннулированный документ номер освобождает. - Оплаты.
POST …/documents/{id}/mark-paidс{ "paid_at": "ГГГГ-ММ-ДД" }— дата из старой системы, а не сегодняшняя. Это метка статуса: если нужна сверка сумм, переносите платежи черезPOST …/documents/{id}/payments. - Акты.
POST …/documents/{id}/create-actсnumberиissue_dateакта, затемPOST …/sign. - Нумерация. Когда история перенесена —
PUT …/numberingсnext_seqдляinvoiceиact, чтобы новые документы продолжили вашу серию (для контрагента со своим форматом — вместе сcounterparty_id). Автоматически это не происходит: счётчик равен числу созданных документов, а не вашему последнему номеру. - Откат. Ошибочный документ —
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) — оплаты суммой (наличные) остаются. - Тип операции угадывается: правило организации (
…/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— снапшот применяется к документу целиком, суммы пересчитывает сервер; - Отклонить:
POST …/proposals/{pid}/decline(можно с причиной вmessage); - Встречный вариант:
POST …/proposals/{pid}/counterсpayload— исходное предложение помечается «заменено», а решение переходит к другой стороне (сравниваются два последних варианта).
Флаг outdated в предложении означает, что документ менялся уже после его отправки
— стоит пересмотреть сравнение перед принятием. События вебхука: 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— создать (409webhook_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продолжают работать и относятся к первому вебхуку организации — уже настроенные интеграции менять не нужно.
События (13):
document.created— создан документ;document.sent— документ отправлен на email;document.payment_status_changed— статус оплаты (unpaid|partial|paid);document.sign_status_changed— статус подписи акта;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=withoutс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.
В списке у каждого счёта приходят поля закрывающего акта — act_id, act_number, act_issue_date, act_sign_status (или null, если акт не выписан): по ним видно, что ещё не закрыто, без отдельного
запроса связок.
Каждый документ несёт автора: 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,
"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 — превышен лимит) и телом { "error": "<описание>", "code": "<машинный код>" }.
Поле code — стабильный идентификатор (например rate_limited, not_found, document_protected); в коде опирайтесь на него, а не на
текст error. При 429 приходит заголовок Retry-After (секунды до повтора) и X-RateLimit-*.
Создайте первый ключ в кабинете. Для управления документами из нейросети — раздел MCP для нейросетей.