Logistivo CLI: управляйте логистикой из терминала и из своей ERP

Один каталог команд питает три поверхности: терминальный клиент lg, ИИ-ассистента в панели управления и канал, к которому подключаются внешние ИИ-ассистенты. Возможность пишется один раз и появляется во всех трёх — и теперь у каждого действия записи в панели есть команда, так что ваши скрипты и ваши собственные ИИ-агенты могут выполнять ту же работу, которую ваши сотрудники выполняют на экране. Платежи, юридические согласия и закрытие аккаунта намеренно остаются в браузере. Эта страница — контракт. Таблица команд ниже не написана вручную: она генерируется из живого реестра команд при каждом запросе.

Что такое Logistivo CLI?

Logistivo CLI (lg) — это клиент командной строки, который выполняет работу вашего аккаунта Logistivo — грузы и рейсы, заявки, ставки и тендеры, автопарк и водители, документы, контакты и взаиморасчёты, счета и бухгалтерия, склад и запасы, сотрудники и таможенные тарифы — из терминала, из скрипта или из вашего собственного ИИ-агента. Команды не зашиты в клиент: клиент получает каталог команд с сервера и строит себя по нему. Те же команды можно вызывать по HTTP, ничего не устанавливая, — этим путём идут интеграции с ERP и CRM, — а также через канал MCP, к которому подключаются внешние ИИ-ассистенты.

Что работает сегодня, а что на подходе

Этот раздел намеренно стоит в начале страницы. Самая дорогая ошибка, которую может допустить интеграционная документация, — описать ещё не открытый эндпоинт так, будто он уже работает: интегратор пишет код, эндпоинт возвращает 404, и доверие уже не восстанавливается.

Поскольку контракт заморожен, код интеграции, который вы пишете сегодня, продолжит работать по мере открытия оставшихся частей. Неизвестные вам поля нужно игнорировать; именно это правило позволяет нам добавлять новое, ничего не ломая.

В чём отличие: клиент не знает команд — он строит их по каталогу

Это единственное решение, которое придаёт смысл всему остальному на этой странице. В обычном CLI команды живут внутри клиента; в Logistivo они живут на сервере.

В классической схеме, когда сервер получает новую возможность, клиенту нужен новый релиз, а пользователю — установить его. В промежутке две стороны знают разное — и это самый незаметный вид сбоя в интеграциях: скрипт работает, код выхода равен 0, а команда просто не описывает того, что умеет сервер.

В Logistivo определение команды — это одна запись: её имя, что она делает, её параметры JSON Schema, каким ролям она видна, требует ли она подтверждения и обратим ли её эффект. При запуске lg получает этот каталог и генерирует из него текст справки, флаги и проверку ввода. В тот момент, когда команда добавляется на сервере, lg уже знает о ней — без обновления клиента.

Тот же каталог питает ИИ-ассистента в панели и канал, к которому подключаются внешние ИИ-ассистенты. Возможность пишется один раз и появляется в трёх местах — и все три проходят через один и тот же шлюз ролей, один и тот же шлюз подтверждения и один и тот же журнал аудита. Отдельного перечня команд CLI не существует; если бы он был, три поверхности незаметно разошлись бы в течение полугода.

Правило именования — как растут имена команд

Каталог будет расти. Новые команды не называют как придётся — применяются шесть правил. Мы публикуем их здесь, потому что именно эти имена ваша интеграция будет писать годами, и их предсказуемость для вас кое-чего стоит.

Покрытие: всё, что есть в панели, — через команду

Многие компании подключают Logistivo к собственному ИИ-агенту или среде автоматизации, а агент полезен только тогда, когда может довести работу до конца. Поэтому правило простое: всё, что офисный пользователь может изменить в панели, может изменить и команда — за тем же шлюзом ролей, тем же шлюзом подтверждения и с тем же журналом аудита.

Это измерено, а не заявлено. Каждый эндпоинт записи, стоящий за панелью, сверяется с реестром команд, и эндпоинт считается покрытым, только если какая-либо команда объявляет его своим HTTP-зеркалом или вызывает тот же общий сервис. Из эндпоинтов записи, входящих в охват, покрыто 100%, и ни один не остался недоказанным. У каждого исключённого эндпоинта есть письменно зафиксированная причина — шаги, связанные с идентичностью и платежами, плоскость управления интеграциями, собственные эндпоинты приложения водителя, клиентская телеметрия, мёртвые копии без вызывающих, — а те, что важны интегратору, перечислены ниже.

Команды, принимающие файл, подчиняются тому же правилу: команд в таблице, принимающих документ или фото, — 33, и все они работают из lg, по HTTP и через MCP (см. «Команды, принимающие файлы»).

Управленческая работа тоже покрыта, но за более сильным ключом: командам в таблице, касающимся сотрудников и ролей, настроек компании, банковских счетов, сохранённых карт и ключей интеграции (их 62), нужен ключ интеграции уровня manager. Ключ уровня employee, используемый по умолчанию, выполняет повседневные операции, а в этих командах получает отказ с INSUFFICIENT_KEY_LEVEL.

У аккаунтов аффилиатов (реферальных партнёров) собственный каталог: команд — 55, доменов — 20; это рефералы, лиды, заметки и задачи CRM, презентации для продаж, доходы и выплаты, а также команды аккаунта и управления сотрудниками, которые есть у каждой роли. Команды только для аффилиатов не входят в публичную таблицу ниже, где перечислены три роли панели; аккаунт аффилиата видит их в GET /api/common/commands и в lg commands. Компания, которая одновременно является аффилиатом и грузоотправителем, перевозчиком или таможенным брокером, видит оба набора.

Агенту следует начинать с lg users get (API-имя get_current_user): команда возвращает роль, уровень доступа используемого ключа, фактические разрешения и то, не блокирует ли сейчас запись ожидающее юридическое согласие или неподтверждённый номер телефона, — так агент может предсказать отказ, а не собирать отказы.

Покрытие измеряется отчётом, который сравнивает реестр команд с эндпоинтами записи панели; эндпоинт панели, добавленный без команды, появляется в нём как недоказанный. Если вы нашли в панели действие, недоступное через команду, сообщите об этом в поддержку.

Установка и идентификация

lg — это Node-скрипт из одного файла без зависимостей (Node 18+), опубликованный в npm как @lgstv/lg. Пакет устанавливает две одинаковые команды, lg и logistivo, — используйте logistivo, если имя lg на вашей машине уже занято алиасом. Поскольку это один файл, можно и просто скопировать lg.mjs на ERP-сервер клиента. Для интеграции с ERP/CRM клиент не нужен — те же команды доступны по HTTP.

1. Установка — Нужен только Node 18 или новее; других зависимостей нет. Установите клиент глобально или запускайте его без установки через npx.

2. Вход по коду одобрения — lg login показывает короткий код одобрения и открывает logistivo.com/connect в вашем браузере; по SSH, в CI или с --no-browser он только выводит адрес, который можно открыть на любом устройстве, в том числе на телефоне. Войдите там как обычно, убедитесь, что код на странице совпадает с кодом в вашем терминале, выберите доступ «только чтение» или «чтение и действия» и одобрите. В терминал не вводится ничего секретного: ни пароля, ни ключа, ни одноразового кода. После этого сессия остаётся активной на этом компьютере — закрытие терминала не завершает её, а lg сам продлевает токен доступа до истечения его срока. lg logout завершает сессию и на сервере, а удалить её можно в панели в разделе Settings › Integration keys › AI and CLI connections. lg login --password (email и пароль) остаётся запасным вариантом. Требуется lg 0.11 или новее.

3. Для серверов и CI: ключ, а не код одобрения — На сервере или в задании CI некому одобрить код в браузере. Выпустите ключ в панели (Settings › Integration keys) или командой lg keys create — при желании от имени отдельного сотрудника с узкими правами — и поместите его в LOGISTIVO_TOKEN или сохраните командой lg login --api-key. Пока переменная задана, файл конфигурации никогда не читается. Подробности — в разделе «Подключение с API-ключом».

4. Проверьте установку и изучите каталог — lg commands выводит все команды, которые видит ваша роль, сгруппированные по доменам; lg help выводит глаголы этого домена, а lg help — полный список параметров одной команды с типами и признаком обязательности. Ни один из этих текстов не хранится в клиенте — всё приходит из каталога. Помните, что список зависит от вашей роли: глаголы одного домена у перевозчика и у грузоотправителя не совпадают (например, loads set-status есть только в каталоге перевозчика). Если какого-то глагола нет, сначала проверьте роль, а уже потом установку.

Никогда не помещайте ключ в систему контроля версий, журнал CI или окно чата. Если вы вошли по коду одобрения или с ключом интеграции, lg logout завершает доступ и на сервере; в сессии с паролем (lg login --password) команда удаляет только ЛОКАЛЬНУЮ копию и прямо об этом сообщает. Журнал аудита хранит ваши аргументы, но маскирует как *** любое поле, в имени которого есть token, password, IBAN, card или OTP, а также каждый параметр, который команда объявляет секретным.

Эргономика клиента

Эти возможности упрощают повседневную работу с клиентом `lg`. Ни одна из них не меняет HTTP-контракт — если вы интегрируетесь напрямую по HTTP, этот раздел можно пропустить.

Подключение с API-ключом

ERP, CRM, скрипт или ИИ-агент подключается к Logistivo с ключом интеграции, выпущенным в панели или командой lg keys create. Ключ действует от имени реального сотрудника: роль этого человека определяет список команд, уровень доступа ключа — насколько серьёзной может быть работа, а когда этот человек уходит из компании, ключ прекращает действовать вместе с ним. Синтетического «сервисного аккаунта» нет и не будет — мультитенантная изоляция привязана к пользователю, а мост без владельца — это как раз тот, что годами молча продолжает записывать данные.

1. Выпустите ключ в панели — Settings › Integration keys › New key. Дайте ему имя («ERP nightly sync»), выберите область доступа и уровень доступа и при желании сократите срок действия. Ключ показывается один раз и только на этом экране; сервер его не хранит. Если вы его потеряете, Rotate выпустит новый — старый сразу станет недействительным.

2. Подключитесь из терминала — lg login --api-key проверяет ключ, выводит идентичность и область доступа и загружает каталог команд. lg whoami спрашивает у сервера, от имени какого пользователя, компании и роли и с какой областью доступа вы работаете. lg logout также отзывает ключ на сервере — точно так же, как завершает там сессию, открытую по коду одобрения (этого не может сделать только lg logout в сессии с паролем).

3. Или чистый HTTP — Ключ — это токен доступа, передаваемый в заголовке Authorization: Bearer, и он напрямую работает с тремя эндпоинтами команд. На сервере или в CI поместите его в LOGISTIVO_TOKEN; пока эта переменная задана, файл конфигурации никогда не читается.

4. Или управляйте ключами из терминала: lg keys — lg keys — терминальный двойник экрана панели: те же эндпоинты, тот же шлюз (разрешение на управление сотрудниками, а при вызове с ключом — ключ уровня manager). create, rotate и transfer однократно возвращают новый токен: с --save-profile=NAME он сразу попадает в локальный профиль и никогда не выводится; иначе lg выводит его один раз. В режиме --json токен включается, только если добавить --show-secret, а вызов --json без обоих флагов отклоняется ещё до отправки запроса, чтобы токен не мог молча оказаться в выводе скрипта. rotate, transfer и revoke запрашивают подтверждение (--yes); ключ адресуется по точному имени или uuid, никогда по догадке. Уровень доступа переносится без изменений на ротированный или переданный ключ.

Область доступа применяется ко всей поверхности API, а не только к каталогу команд: ключ только для чтения пропускает запросы на чтение (GET/HEAD/OPTIONS), а на любой другой запрос записи приходит ответ 403 forbidden. Есть два намеренных исключения, и оба защищены более тонким шлюзом: эндпоинт выполнения команд (решение принимается по собственному флагу read_only команды, потому что команды без записи тоже вызываются через POST) и отзыв ключом самого себя (lg logout, разрешён и для ключа только для чтения). Область доступа, доступы к доменам и уровень доступа — три независимые оси, и все они должны быть пройдены: ключ read не может записывать даже на уровне manager, ключ write уровня employee не может управлять сотрудниками, а доступ к домену loads:write не открывает управленческую работу. Выпускайте ключ от имени сотрудника с наименьшими правами, которых достаточно для ваших задач: роль определяет, какие записи вы вообще можете видеть.

Первые десять минут: от панели до первого вызова

Самые дорогие минуты интеграции — первые десять. Четыре шага ниже выстраивают этот путь по порядку; тот же порядок есть в панели в виде мастера (Settings › Integration › Setup wizard), и мастер вызывает ровно те эндпоинты, что описаны здесь. Разработчик без доступа к мастеру придёт в ту же точку, прочитав этот раздел.

Ни один из четырёх шагов не заканчивается словами «я отправил». На вопрос, действительно ли ключ работает, отвечает журнал запросов; на вопрос, действительно ли доходит вебхук, — журнал доставок. Оба открыты в панели и в lg. Настройка, которую никто не измерял, — это настройка, в работоспособность которой просто верят.

Песочница: практикуйтесь в записи, не затрагивая продакшен

Предварительная проверка (dry_run) говорит, что команда СДЕЛАЛА БЫ; но действительно ли идемпотентность воспроизводит результат, действительно ли вебхук доставляется, как выглядят ветки ошибок и откуда берутся кредиты, можно увидеть только при РЕАЛЬНОЙ записи. Для этого и существует песочница: отдельный тенант, зеркалирующий вашу компанию, — тот же код, тот же каталог команд, — который никогда не касается вашего боевого учёта.

Песочница — это не флаг запроса, а ТЕНАНТ. Отдельная компания Logistivo, открытая для вашей: собственный пользователь, собственный пакет, собственный журнал кредитов, собственные подписки на вебхуки. Среда ВЫВОДИТСЯ из компании ключа и не может быть передана вызывающей стороной, поэтому, даже если вы выберете не тот профиль, запрос остановится на сервере с 403 ENVIRONMENT_MISMATCH. Ситуация «я думал, что пишу в песочницу, а записал в продакшен» невозможна по построению.

Поверхность идентична — это измерено: каталог, который получает ключ песочницы, — тот же, что получает ваш боевой ключ (для ключа грузоотправителя число команд с обеих сторон — 448). Мост, который работает в песочнице, работает и в продакшене; ничему не придётся учиться дважды.

Песочница открывается не пустой: в ней есть сгенерированные (НЕ скопированные) начальные данные — контакты, грузы в обоих направлениях и заявки. Имена откровенно вымышлены, а e-mail-адреса находятся в домене @sandbox.invalid; ни одна строка реального тенанта никогда не читается, не клонируется и не используется как шаблон.

Кнопки сброса намеренно НЕТ, и никакие записи, файлы или документы никогда не удаляются. Если вам нужен чистый старт, обновление открывает новое поколение, а старое остаётся — выведенным из работы. Несколько сотен накопившихся репетиционных строк ничего не стоят по сравнению с процедурой удаления, направленной не на тот тенант.

Модель команд

Грамматика одна, и исключений у неё нет. Чтобы знать, как вызвать команду, достаточно того, что о ней говорит каталог.

lg loads list --status=in_transport --limit=20 --json

Имена флагов — это имена параметров схемы, и lg принимает оба написания: --load_code и --load-code попадают в одно и то же поле (lg help выводит форму в kebab-case). Поэтому по флагу сразу видно, в какое поле JSON он попадёт, и переход от CLI к HTTP не требует перевода имён. Булев параметр, записанный без значения, равен true (--only_open); противоположное значение — --no-only_open. Аргумент, значение которого начинается с дефиса, пишите как --flag=value, иначе забытый флаг молча проглотит следующий как своё значение. В команде, принимающей файл, ставьте @ перед локальным путём (--attachment_uuid=@./cmr.pdf) — см. «Команды, принимающие файлы».

Реальные примеры

Каждая команда ниже уже сегодня существует в каталоге; ни одно имя или параметр здесь не выдуманы. Вывод сокращён. В выводе показан уровень `data.result`; по HTTP тот же объект приходит внутри конверта `{success, data:{…}}` (см. HTTP API команд).

Без клиента: HTTP API команд

lg — это удобство, а не единственный вход. И каталог, и выполнение проходят через три HTTP-эндпоинта (у файлов есть четвёртый — эндпоинт загрузки, см. «Команды, принимающие файлы»); интеграция с ERP или CRM подключается сюда напрямую. Идентификация — персональный токен доступа в заголовке Authorization: Bearer. КАЖДЫЙ ответ приходит в одном и том же конверте: `{ "success": bool, "data": { … }, "code": "…", "message": { … } }`. Весь контракт находится внутри `data` — `contract_version`, `command`, `status` и, как результат работы команды, `result`; при сбое — `error_code`, `error_key` и `error`. Примеры ответов на этой странице для экономии места показывают объект `data`, а не конверт; не забудьте развернуть `data` в своём коде. `lg --json` тоже выводит конверт ЦЕЛИКОМ, поэтому пути jq начинаются с `.data.result…`. Единственное исключение — `/api/public/status`: этот эндпоинт отвечает без конверта, чтобы сторожевые скрипты могли поднимать тревогу, не разбирая ответ.

Запрашивать подтверждение ответом 200 + ok:false запрещено, и так никогда не будет. Причина проста: клиенты считают 200 успехом, поэтому запрос подтверждения, возвращённый как 200, молча оборвал бы поток, и никто бы этого не заметил. Подтверждение — это всегда 409.

Команды, принимающие файлы

Команд в таблице, принимающих документ или фото, — 33: прикрепить CMR к грузу, прочитать коды HS из счёта, создать транспортное средство по его регистрационному документу, оценить паллету по фото, загрузить документ компании, сверить счёт с упаковочным листом. В браузере вы перетаскиваете файл в форму; во всех остальных местах нужны два шага — загрузить файл, а затем передать его id команде.

Параметр принимает файл, если его имя оканчивается на attachment_uuid или attachment_uuids — photos[].attachment_uuid внутри списка, front_attachment_uuid у банковского чека. Запоминать это не нужно: каждая строка каталога содержит file_params с точными путями, схема помечает эти поля (format: uuid, x-logistivo-file: true), а ответ каталога содержит блок attachments с адресом загрузки, допустимыми типами, ограничениями размера и временем жизни. lg help тоже их показывает.

Загрузка — это запись, поэтому ключ с областью read загружать не может; любой ключ с областью write может — на любом уровне доступа. Загрузки принадлежат пользователю, а не ключу: два ключа одного пользователя видят одни и те же загрузки, и никто не может использовать чужие. id, который неизвестен, истёк или вам не принадлежит, просто не находится, а причина не раскрывается.

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

Загрузка — это не команда, и сама по себе она не имеет бизнес-эффекта: с вашими данными ничего не происходит, пока команда не использует id. Поэтому загрузка никогда и не запрашивает подтверждения — шлюз несёт команда, которая использует файл.

Шлюз подтверждения и необратимые команды

Каждая команда в каталоге несёт два флага: confirmation_needed (требует ли она явного одобрения перед запуском) и irreversible (можно ли отменить её эффект). Это разные вопросы, и оба контролируются на сервере, а не оставлены на добросовестность клиента.

Шлюз — это вопрос ответственности, а не бизнес-правил. Ставка — первый шаг к договору перевозки; выставление счёта создаёт бухгалтерскую проводку; движение запасов меняет учётный регистр. Ничто из этого никогда не должно случайно выполняться дважды.

Шлюз определяется по ФАКТИЧЕСКИ выполняемой команде. Когда одна команда оборачивает другую — как это делает универсальный исполнитель в чате, — шлюз берётся из флагов той команды, которая реально будет выполнена, а не из обёртки. При фиксированном флаге один из двух сбоев был бы неизбежен: либо команда выставления счёта выполнялась бы без подтверждения, либо каждый поиск страны вызывал бы карточку подтверждения.

read_only и отсутствие шлюза — не одно и то же. Есть три разных состояния: invoices preview не имеет шлюза и ничего не записывает; loads create имеет шлюз и записывает; invoices create не имеет шлюза, но записывает — появляется черновик. Всё решает один тест: когда команда вернула результат, изменилась ли какая-нибудь строка в базе данных?

Некоторые действия находятся не за этим шлюзом, потому что они вообще не являются командами: покупка пакета, добавление карты с 3D Secure, принятие юридических текстов и закрытие аккаунта происходят в собственном браузере человека. Для них lg handoff-links generate возвращает ссылку на logistivo.com — бесплатно, ничего не записывая и не содержа секрета, — и человек завершает шаг там. Никакой флаг, как бы он ни был выставлен, не превращает платёж или согласие в вызов API.

Технически интеграция может автоматически проходить шлюз при каждом вызове, но делать так не рекомендуется. Правильный подход — автоматизировать шлюз только для команд, о которых известно, что они обратимы, а необратимые направлять в очередь к человеку.

Предварительная проверка: что сделала бы команда записи — без её выполнения

Самый дорогой шаг при подключении моста к ERP — выяснять, правильно ли составлено тело запроса, ДЕЙСТВИТЕЛЬНО выполняя команду: черновик счёта в боевом учёте, сожжённый кредит, ручная уборка. dry_run положил этому конец: вы добавляете в запрос одно поле, команда не выполняется, и вы читаете, что произошло бы.

Сейчас в реестре команд — 519; из них только для чтения (ничего не записывают) — 233. Из оставшихся команд записи — их 286 — шлюз есть у 251: при вызове без confirm они уже отвечают 409 и описывают, что сделали бы. Пробел составляли остальные — их 35: черновики счетов, черновики экспортных документов, строки позиций — ровно та цепочка, которую мост подключает первой. Для них вызвать значило выполнить.

Был и второй пробел, даже в командах со шлюзом: валидация схемы выполняется ПОСЛЕ шлюза. Поэтому вызов с отсутствующим аргументом сначала получал карточку подтверждения, подтверждал её и только потом слышал «этого поля не хватает». Предварительная проверка отвечает на оба вопроса за один обмен запросом и ответом.

А самая частая — и обнаруживаемая позже всех — ошибка моста такова: вы отправляете поле, которого нет в схеме (customer_id там, где ожидается recipient_contact_id), сервер молча его отбрасывает, команда возвращает 200, и запись оказывается не там. Ответ предварительной проверки перечисляет такие поля по именам в arguments.ignored; никакое сообщение об ошибке этого никогда не сделает.

Предварительная проверка ничего не записывает, и это прежде всего свойство архитектуры, а уже потом результат измерения: путь предварительной проверки никогда не входит в обработчик команды, поэтому это верно для каждой команды, включая добавленные позже. При выпуске это ещё и измерили: был перехвачен каждый SQL-оператор, выданный при предварительной проверке каждой команды записи, существовавшей тогда в каталоге (два набора аргументов, две роли, 178 предварительных проверок). Ни одного INSERT/UPDATE/DELETE, ни одного поставленного в очередь задания, ни одного письма; количество строк в 31 таблице и баланс кредитов не изменились.

Код выхода тот же, что дал бы реальный вызов: выполнилась бы — 0, шлюз подтверждения — 4, конфликт идемпотентности — 2, всё остальное — 1, поэтому «lg … --dry-run || exit» — осмысленный шлюз в шаге CI.

Идемпотентность: почему повтор не создаёт дубликатов

ERP не читает ваше сообщение об ошибке, не видит ваш экран и повторно отправит тот же запрос после тайм-аута. Этот раздел существует именно из-за такого поведения.

Вызов с idempotency_key выполняется не более одного раза. Второй вызов не выполняет команду заново, а возвращает записанный результат со статусом replayed. Когда обрывается сеть, когда запрос завершается по тайм-ауту или когда очередь дважды забирает одно и то же задание, вы получаете первый результат, а не дубликат счёта.

Используйте ключ в каждой команде записи. Поле каталога read_only даёт вам это различие даром: каждая команда с read_only: false заслуживает ключа идемпотентности.

Ваша стратегия повторов должна использовать экспоненциальную задержку (exponential backoff) и передавать ОДИН И ТОТ ЖЕ ключ при каждой попытке. Генерировать новый ключ для каждой попытки — всё равно что вообще не использовать идемпотентность. Не отправляйте один и тот же ключ параллельно: «не более одного раза» гарантируется для последовательных повторов, а не для двух запросов, соревнующихся друг с другом.

Инкрементальная синхронизация и пагинация

Мост ERP/CRM должен забирать то, что изменилось, а не всё подряд каждый час. Каждая команда списка — loads list, demands list-own, contacts list, invoices list, teammates list, load-groups list, reservations list, drivers list, tenders list, products search, warehouses search — принимает два общих параметра и возвращает одно общее поле.

Почему нет offset: на живой таблице смещение «плывёт». Строка, вставленная между двумя страницами, заставляет вас пропустить запись или учесть её дважды — молча. Курсор движется только вперёд, поэтому запись, вставленная или обновлённая посреди обхода, попадает в конец списка: ничего не теряется и ничего не дублируется.

Полная начальная загрузка работает по тому же механизму: начните с updated_since=1970-01-01T00:00:00Z и следуйте по next_cursor, пока он не станет null. Отдельного эндпоинта «export» нет, потому что он не нужен.

То же самое с lg: lg loads list --updated_since=2026-09-01T00:00:00Z --limit=100 --json, затем lg loads list --cursor= --json.

Для следующей синхронизации установите updated_since равным наибольшему updated_at, который вы видели. Метки времени имеют точность до секунды, поэтому запись на границе может прийти дважды — это безвредно, если на своей стороне вы делаете upsert по external_ref или коду. Без updated_since или cursor команда работает в режиме чата: порядок, удобный человеку, не более 20 строк, next_cursor равен null, а truncated сообщает, что «есть ещё». Не используйте этот режим для синхронизации.

Машиночитаемые ключи ошибок для этого раздела: INVALID_CURSOR, INVALID_UPDATED_SINCE, DUPLICATE_EXTERNAL_REF, EXTERNAL_REF_TOO_LONG, EXTERNAL_REF_INVALID. Все они отвечают HTTP 422 с error_code: "failed" и result.error_key в теле. Стройте ветвление по ключу, а не по тексту.

Ваш собственный ключ: external_ref

Не делайте наши автоинкрементные id (invoice_id, contact_id) бизнес-ключом в своей ERP: они непрозрачны и существуют только в пределах вашей компании. Вместо этого грузы, заявки, контакты, счета, товары и склады хранят ваш ключ.

Исходящие вебхуки: мы отправляем вам POST, когда что-то происходит

Всё до этого места было pull-моделью: вы спрашиваете, мы отвечаем. Мосту ERP приходилось спрашивать раз в минуту, чтобы узнать, что груз сменил статус, выставлен счёт или пришла ставка. Теперь это не нужно: зарегистрируйте https-адрес, выберите интересующие вас события — и когда они происходят, приходит подписанный POST.

Push добавлен РЯДОМ с pull, а не вместо него. Инкрементальная синхронизация (updated_since + cursor) не тронута и по-прежнему остаётся правильным инструментом: первая полная загрузка, дозагрузка истории и ответ на вопрос «не пропустил ли я что-нибудь» — всё это там. Вебхуки сокращают задержку с минут до секунд. Здоровый мост использует и то и другое: вебхук — как триггер, синхронизацию — как страховочную сеть.

Payload — это СИГНАЛ, а не копия записи. Он несёт идентификаторы (id, ваш собственный external_ref, статус, время); подробности вы получаете своим ключом через GET, и этот вызов уже проходит ваши собственные шлюзы авторизации. Поэтому поверхность раскрытия данных остаётся на уровне идентификаторов. Два поля намеренно отсутствуют: сведения о том, какая компания сделала ставку (слепые ставки отображаются в панели под псевдонимом; передача этих сведений в вебхуке нарушила бы эту конфиденциальность через второй канал), и контактные данные бронирования (незачем передавать персональные данные через дополнительный канал).

Имена событий ЗАМОРОЖЕНЫ. Если смысл имени меняется, открывается новое имя, а старое доживает период вывода из употребления (deprecation). Добавление нового поля в payload НЕ повышает версию контракта — потребитель обязан игнорировать неизвестные ему поля.

Каталог событий

За каждым из событий ниже (их 13) стоит класс события, который ДЕЙСТВИТЕЛЬНО отправляется. В каталоге нет имён из разряда «было бы неплохо»: событие, которое анонсировано, но никогда не срабатывает, — это событие, которого интегратор ждёт вечно, и такой сбой незаметен. Эта таблица тоже не написана вручную — она строится из живого каталога при каждой загрузке страницы.

invoice.payment_recorded намеренно ОТСУТСТВУЕТ: класс этого события существует в коде, но нигде не отправляется. Событие оплаты будет добавлено в тот день, когда класс действительно начнёт срабатывать, — не раньше. Анонсировать событие заранее хуже, чем не добавлять его вовсе.

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

Каждый запрос содержит заголовок X-Logistivo-Signature: t=,v1=. Подписываемая строка — "." (метка времени, точка и сырое тело), алгоритм — HMAC-SHA256. Префикс v1 позволяет версионировать схему: если когда-нибудь появится вторая схема, заголовок будет содержать t=…,v1=…,v2=…, и вы выберете ту, которую знаете.

Мы публикуем два эталонных приёмника без зависимостей — для Node и для чистого PHP. Оба применяют четыре правила выше и проверяют себя флагом --selftest: корректная подпись, подменённое тело, неверный секрет, метка времени вне окна, некорректный заголовок, ловушка повторной сериализации и оба вида повторения (та же доставка / тот же факт). Верификатор — точный аналог кода, который используем мы сами: мы потребляем тот же контракт, который публикуем.

Доставка, повторы и приостановка

Ваш приёмник не всегда будет доступен. Канал доставки исходит из этого: временный сбой попадает в график повторов, постоянный — приостанавливает подписку (muting), и ПРИЧИНА приостановки всегда записывается.

Причина приостановки машиночитаема и стабильна (delivery_failures, endpoint_gone, unsafe_target, key_revoked, by_user) и отображается как в панели, так и в `lg webhooks list`. Приостановка без причины оставила бы вопрос «почему ничего не приходит» без ответа. Повторное включение сбрасывает счётчик и отметку.

Адрес пишете вы; запрос делает НАШ сервер. Без шлюза тенант, указавший адрес облачных метаданных, мог бы заставить нас отправлять запросы изнутри нашей собственной сети. Поэтому адрес проверяется и при регистрации, и при КАЖДОЙ доставке:

Работоспособность и статус: никто не должен заметить замолчавший мост раньше вас

Интеграционный мост редко умирает громко. Обычно он затихает глубокой ночью: истекает токен, человек, от имени которого работает ключ, увольняется, область доступа сужается или принимающий эндпоинт начинает возвращать 403. Первым это почти всегда замечает клиент. lg status существует, чтобы изменить этот порядок.

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

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

Та же сводка показывается в панели: в разделе Settings › Integration › Keys у каждой строки есть значок, а под ним — причина. Пороги не вычисляются на стороне клиента; какой уровень назначил сервер, тот и отображают и панель, и lg status, поэтому два экрана никогда не скажут об одном и том же ключе разное.

Вашему сторожевому скрипту не нужен токен. Эндпоинт возвращает только слова статуса; он не содержит ни версии, ни имени хоста, ни имени очереди, ни числа заданий, ни текста ошибок, ни данных клиентов — и не может их содержать.

Сводка работоспособности формируется постоянным числом запросов к базе на один HTTP-запрос; это число не растёт с количеством ключей. За подробностями читайте последние запросы конкретного ключа через lg logs, а исходящий мост — через lg webhooks list.

Ошибки и коды выхода

Машина ветвится по стабильной строке. Текст в поле error предназначен для человека, локализован и меняется без предупреждения — интеграция, которая ветвится по тексту сообщения, не поддерживается, и её поломка не считается багом.

Две таксономии, два поля. error_code — транспортный уровень (таблица выше), и ветвиться в первую очередь нужно по нему. error_key — стабильный ключ бизнес-правила внутри failed (INSUFFICIENT_CREDITS, NOT_FOUND, DUPLICATE_CONTACT, INSUFFICIENT_STOCK), публикуемый в виде словаря в каталоге (error_keys) и ниже. Команда, которая ещё не выдаёт ключ, возвращает error_key: null; ключ, отсутствующий в словаре, считайте общей ошибкой.

Обещание стабильности

Самый дорогой вопрос в интеграционном контракте — «будет ли код, который я пишу сегодня, работать завтра?». На него отвечает одно число: contract_version в каталоге и заголовок X-Contract-Version в каждом ответе. Оно отвечает ровно на один вопрос: работает ли ранее написанный код без изменений? Если число не выросло — да. Если выросло — нет: что-то, на что вы опирались, было удалено или сужено.

Потому что объявление ничего не удаляет: на следующий день ваш существующий код работает в точности как прежде. Повышение версии при объявлении дало бы два сигнала на одно изменение и приучило бы вас игнорировать первый — ослабляя тот сигнал, который действительно важен. Объявление и так приходит вместе с вызовом, который вы делаете: deprecated_at и replaced_by в строке каталога, заголовок X-Deprecated плюс блок deprecated в ответе этой команды и однострочное предупреждение в stderr, если вы используете lg. Вам не нужно ничего делать, чтобы узнать, что что-то уходит; это уже есть в вашем журнале.

Между объявлением и удалением проходит не менее двух календарных кварталов. Всё это время старая команда по-прежнему отвечает и предупреждает при каждом вызове. Когда имя CLI выводится из употребления, его преемник забирает это имя как алиас: в течение окна старое имя ведёт к старой команде, а в день удаления тот же вызов попадает в новую — без того, чтобы вы выпускали новую версию клиента.

preview_invoice → get_invoice (объявлено 3 сентября 2026 г.). get_invoice возвращает тот же блок предварительного просмотра и добавляет позиции, разбивку налогов, зарегистрированные платежи, остаток к оплате и обращение по вашему собственному ключу ERP (external_ref). preview_invoice продолжает работать, как и lg invoices preview, — оба предупреждают.

Таблица идентификаторов команд

Таблица ниже не написана вручную: она генерируется из живого реестра команд при каждой отдаче этой страницы. Команда, добавленная на сервере, появляется здесь сама. Колонка «Шлюз» показывает, запрашивает ли команда подтверждение и можно ли отменить её эффект; колонка «Роли» — в каталоге каких типов аккаунтов её можно найти. Значки отмечают команды, которым нужен ключ уровня manager, команды, принимающие файл, и команды, которые выполняются только из lg.

API-имя (list_loads) заморожено и не изменится; именно его вы используете в HTTP-вызовах. Идентификатор CLI (loads list) подчиняется политике версионирования: после объявления он меняется только с повышением версии и переходным окном, в течение которого каталог отдаёт старую пару как алиас. Описания здесь взяты из собственной записи каждой команды и сокращены — полный текст, типы параметров и значения enum смотрите в эндпоинте каталога или запустите lg help . Команды только для аффилиатов здесь не перечислены; аккаунт аффилиата видит их в собственном каталоге.

Интеграция с ERP и CRM — Идентичность: никаких сервисных аккаунтов — вместо них реальный пользователь

ERP или CRM подключается к Logistivo как реальный пользователь компании с персональным токеном доступа, выпущенным в панели. Синтетический сервисный аккаунт — без компании, освобождённый от ограничения областью видимости, без ответственного лица — никогда не создаётся.

Это архитектура, а не предпочтение. Мультитенантная изоляция целиком опирается на компанию пользователя; для идентичности без компании область изоляции перестаёт действовать, и один неверный запрос с таким токеном прочитает данные, которые ему не принадлежат. Вторая причина — аудит: колонка пользователя в журнале связывает «кто это выполнил» с человеком; сервисный аккаунт лишает эту колонку смысла и оставляет интеграцию, выставляющую счета, без владельца. Третья — отзыв: если токен принадлежит человеку, интеграция замолкает, когда этот человек уходит. Мост без владельца — это мост, который годами тихо продолжает записывать данные.

Интеграция с ERP и CRM — Версионирование и устаревание

Ответ каталога содержит целое число contract_version; строки команд содержат since, deprecated_at и replaced_by. Версия контракта растёт только при ломающих изменениях.

Интеграция с ERP и CRM — Стабильные идентификаторы: никогда не отдавайте своей ERP автоинкрементный id в качестве ключа

Внутренние автоинкрементные id непрозрачны и ограничены тенантом: корректный id другой компании возвращает вам «не найдено», а не данные. Но непрозрачность не делает их бизнес-ключами. То, что ERP хранит в собственной записи, должно быть бизнес-ключом.

Интеграция с ERP и CRM — Пагинация, фильтрация и инкрементальная синхронизация

Команды списков принимают limit и возвращают count. Для ночной синхронизации контракт обязуется соблюдать следующее; команда списка, которая это внедрила, объявляет cursor и updated_since в своей JSON Schema, а пока этого нет, считайте её небольшим окном (максимум 20 строк, без курсора).

Интеграция с ERP и CRM — Ограничения частоты запросов

Ключ лимита — токен, а не IP: ERP приходит из-за одного NAT, и лимит по IP считал бы всю компанию одним пользователем. Две интеграции с двумя ключами никогда не съедают квоту друг друга — это вторая причина правила «одна интеграция — один ключ».

Интеграция с ERP и CRM — Чего мы делать не будем

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

Машиночитаемый каталог

Эта страница написана для людей. Для ИИ-ассистента или автоматизированного клиента та же информация существует в виде структурированной копии, не требующей аутентификации.

GET /api/public/cli/catalog не требует аутентификации и возвращает публичную идентичность команд: замороженное API-имя, идентификатор CLI domain/verb, описание, параметры JSON Schema, флаги read_only, confirmation_needed и irreversible, credit_cost, а также поля доступа key_level, permission, file_params, secret_parameters и cli_only. Он не возвращает ни персональных данных, ни данных тенантов, ни образцов записей — только то, из чего состоит поверхность.

Правильный способ использования для внешнего агента: прочитать каталог, объяснить СВОИМИ словами, какие задачи возможны, а когда нужно выполнение — предложить пользователю запустить его в собственной среде с собственным токеном. Агент никогда не запрашивает, не генерирует и не передаёт токен пользователя.

Подключение Logistivo к ИИ-ассистенту для регистрации и заявок на перевозку — это отдельный канал со своей страницей:

Машиночитаемое описание: OpenAPI 3.1 и Postman

Каталог выше — собственный формат Logistivo. Та же информация публикуется и в двух отраслевых стандартных форматах, которые уже понимают ваши инструменты: описание OpenAPI 3.1 и коллекция Postman. Ни одно из них не написано вручную — оба генерируются из живого реестра команд при каждом запросе, поэтому устареть не могут.

Описание — это и ЕСТЬ каталог, а не вторая реальность. Каждая команда представлена как операция POST /api/common/commands/; схема тела — собственная JSON Schema команды (байт в байт то, что видит ассистент), схема ответа — общий конверт, флаги шлюзов (read_only, confirmation_needed, irreversible и значок кредитов) находятся в расширении x-logistivo, а ключи бизнес-ошибок — в x-error-keys. Выведенная из употребления команда помечается deprecated: true и остаётся вызываемой в течение своего шестимесячного окна.

Помимо командной поверхности, в описание входят и REST-эндпоинты, несущие интеграционный контракт: 10 (эндпоинты списков с инкрементальной синхронизацией: updated_since + cursor + limit) и 7 (эндпоинты записи, принимающие ваш собственный ключ external_ref). REST-поверхность Logistivo гораздо больше; всё остальное исключено НАМЕРЕННО. Эти payload — собственный контракт продукта, и схема, написанная наугад, сломала бы ваш сгенерированный клиент на первом же вызове — а ошибку вы искали бы в своём коде, а не в нашем.

Описание генерируется для РОЛИ того, кто запрашивает: команда другой роли никогда не попадает в документ, потому что описание — это контракт, а не словарь, и всё внутри него должно быть вызываемым с этим токеном. Версия без аутентификации — объединение трёх ролей, и там каждая операция указывает в x-logistivo.roles, каким ролям она видна.

Не верьте нам на слово — проверьте: npx @redocly/cli lint logistivo-openapi.json проходит без единого предупреждения.

Генерация кода: типизированный клиент из описания

Описание у вас есть; теперь сгенерируйте из него работающий код. `lg codegen` читает документ OpenAPI и записывает однофайловый клиент без зависимостей — на TypeScript (объявления типов плюс тонкая обёртка вызовов) или на PHP (класс клиента с формами массивов в PHPDoc). Никакого `npm install`, никакого composer: `fetch` на стороне TypeScript, `ext-curl` на стороне PHP.

Генерация происходит на ВАШЕЙ машине, а не на нашем сервере. Это не вопрос вкуса: закрепите описание в своём репозитории один раз (`lg spec --out=...`), и одна и та же команда будет выполняться при каждой сборке без сети и без токена, так что ваш CI никогда не зависит от доступности наших серверов. А поскольку сгенерированный код — это код, который РАБОТАЕТ в вашей ERP, хранение его в вашем репозитории, а не получение через API, означает, что каждое изменение видно в `git diff`.

Сгенерированный клиент правильно обрабатывает все три канала Logistivo и узнаёт, какой из них какой, из документа, а не угадывает: каждая операция объявляет свой канал в `x-logistivo.surface`. Командный канал разворачивает конверт `{success, data:{…}}`; REST-список синхронизации НЕ обёрнут в конверт и возвращается как есть (если его развернуть, потеряется `next_cursor`); REST-запись возвращает собственное продуктовое тело эндпоинта. Генератор общего назначения этого различия не знает — наш знает.

Типы не угаданы: интерфейс аргументов каждой команды генерируется из той самой JSON Schema, которую видит ассистент. Обязательное остаётся обязательным, enum становятся объединёнными типами (union), вложенные объекты — вложенными типами. На практике: если сервер требует поле, вызов, в котором оно забыто, падает не с 422 в продакшене, а на этапе компиляции.

Метка времени генерации в файл не записывается: одно и то же описание с одними и теми же флагами даёт одни и те же байты. Поэтому повторная генерация не создаёт шума в `git diff`, а единственное, что действительно изменилось, — контракт — остаётся на виду.

Честные границы

Мы скорее расширим этот раздел, чем сократим его. Ценность интеграционного контракта не столько в том, что он обещает, сколько в том, насколько ясно он говорит, чего не делает.

Часто задаваемые вопросы

Для чего используется Logistivo CLI?

Он выполняет работу вашего аккаунта Logistivo из терминала, скрипта или вашего собственного ИИ-агента: грузы и рейсы, заявки, ставки и тендеры, автопарк, водители и их расходы, документы, контакты и взаиморасчёты, счета и бухгалтерия, склад и запасы, сотрудники, тарифы HS и антидемпинговые пошлины. У каждого действия записи в панели есть команда; платежи, юридические согласия и закрытие аккаунта намеренно остаются в браузере. Те же команды можно вызывать по HTTP для интеграций с ERP/CRM и через канал MCP.

Как называются команды в Logistivo CLI?

Форма такая: lg --parameter=value. Домен — сущность во множественном числе (loads, demands, invoices, export-documents), а глагол берётся из закрытого словаря, который пополняется только по осознанному решению (list, get, create, update, search, set-status, generate и так далее). Все глаголы одной сущности живут в одном домене, а пара (domain, verb) уникальна во всём каталоге.

Как пройти аутентификацию в CLI?

В интерактивном режиме lg login показывает код одобрения, который вы подтверждаете в браузере после входа в Logistivo, — ни пароль, ни ключ в терминал не вводятся, а сессия остаётся активной и сама продлевается, пока вы не выполните lg logout. lg login --password (email и пароль) остаётся запасным вариантом. На серверах и в CI используйте ключ интеграции, выпущенный в панели или командой lg keys create: lg login --api-key сохраняет его в локальный профиль; либо поместите его в переменную окружения LOGISTIVO_TOKEN. По HTTP токен передаётся в заголовке Authorization: Bearer.

Можно ли создать сервисный аккаунт для интеграции с ERP?

Нет. Интеграция подключается с токеном реального пользователя компании, созданного в панели. Синтетическая идентичность без компании выводит из строя мультитенантную изоляцию, лишает смысла колонку «кто это выполнил» в журнале аудита и оставляет мост, который не замолкает, когда его владелец уходит. Правильный подход — отдельный пользователь с узкой ролью и названным ответственным.

Есть ли у Logistivo тестовая среда (песочница)?

Да, и она открывается самостоятельно: для вашей компании создаётся отдельный тенант-песочница — тот же код, тот же каталог команд, собственный журнал кредитов. Среда определяется по компании ключа и не может быть передана в запросе; вызов с неверным профилем останавливается на сервере с 403 ENVIRONMENT_MISMATCH. Заявка, открытая в песочнице, никогда не доходит до реальных перевозчиков, событие песочницы никогда не доставляется в вашу боевую подписку на вебхуки, а ваш боевой баланс не меняется. Эндпоинты: POST /api/common/integration-sandbox (открыть), POST /api/common/integration-sandbox/keys (ключ), GET /api/common/integration-sandbox/go-live (чек-лист перехода в боевой режим). На стороне lg флаги lg --sandbox и lg --live выбирают профиль для одного запуска.

Как читать вывод lg --json в скрипте?

Вывод — это весь HTTP-конверт: { "success": bool, "data": { "contract_version", "command", "status", "result" }, "code", "message" }. Бизнес-данные лежат в .data.result; в командах списков строки — это .data.result., количество — .data.result.count, а следующая страница — .data.result.next_cursor. Поэтому правильный путь jq выглядит так: lg loads list --json | jq -r '.data.result.loads[].load_code'. Путь, пропускающий конверт, не выдаёт ошибку, а молча возвращает null — в сторожевом скрипте это превращается в тревогу, которая никогда не перестаёт срабатывать. Единственное исключение — /api/public/status, тело которого намеренно отдаётся без конверта.

Если отправить один и тот же запрос дважды, появятся ли дубликаты записей?

Нет, если вы отправили idempotency_key. Второй вызов с тем же ключом не выполняет команду заново, а возвращает первый результат со статусом replayed. Ключ должен быть UUID и обязан оставаться ОДНИМ И ТЕМ ЖЕ во всех повторах — генерировать новый ключ для каждой попытки всё равно что не использовать идемпотентность.

Что делает --yes?

Проходит шлюз подтверждения. Когда команда со шлюзом вызывается без --yes, ничего не меняется: сервер одним предложением описывает, что собирается сделать, сообщает, необратимо ли это, и останавливается (HTTP 409, код выхода CLI 4). Повтор команды с --yes выполняет работу.

Как обнаружить неудачную команду в скрипте?

Читайте код выхода, а не текст сообщения: 0 — успех, 1 — отказ по бизнес-правилу, 2 — ошибка команды или аргумента, 3 — ошибка аутентификации или роли, 4 — требуется подтверждение. По HTTP эквиваленты — 200, 422, 404, 403 и 409, а поле error_code в теле стабильно. СООБЩЕНИЕ об ошибке локализовано и меняется без предупреждения.

Как программно узнать доступные команды?

GET /api/public/cli/catalog — для обнаружения без аутентификации, и GET /api/common/commands — для списка, который действительно видит ваш аккаунт. Оба возвращают параметры JSON Schema каждой команды; стройте клиент на их основе, а не зашивайте список команд в код.

Нужно ли обновлять клиент при добавлении новой команды?

Нет. Клиент получает список команд из каталога, поэтому команда, добавленная на сервере, появляется без нового релиза клиента. Обновление нужно только при ломающем изменении, которое повышает версию контракта, и в этом случае старая форма остаётся в силе не менее шести месяцев.

Заменяет ли CLI существующий REST API?

Нет. REST-поверхность, на которой работают веб- и мобильные приложения, остаётся на месте. Каталог команд не заменяет CRUD ресурсов; это уровень глаголов — единый способ выполнить действие, проходящий через шлюз ролей, шлюз подтверждения и аудит.

Как получить API-ключ Logistivo и что он может?

Менеджер с разрешением на управление сотрудниками выпускает его в панели в разделе Settings › Integration keys или из терминала командой lg keys create. Ключ действует от имени реального сотрудника и имеет три независимые настройки: область доступа (read открывает каталог и команды без записи, write открывает все команды — шлюз подтверждения остаётся прежним), необязательные доступы к доменам (domain grants) и уровень доступа — employee, по умолчанию, для повседневных операций или manager — для управления сотрудниками, настройками, ключами и деньгами. Ключ показывается один раз и не хранится на сервере; его можно ротировать, передавать и отзывать. Когда сотрудник, от имени которого он действует, уходит, ключ тоже закрывается. Сервисного аккаунта нет.

Что будет, если повторно использовать ключ идемпотентности для другой команды?

Сервер отвечает 409 idempotency_conflict и называет команду, для которой ключ был использован впервые; ничего не выполняется и ничего не воспроизводится. Ключи ограничены вашей компанией, привязаны к одной команде и блокируются на 24 часа — после этого тот же ключ запускает новое выполнение.

Как к моей интеграции применяются ограничения частоты запросов?

На уровне токена, в минуту, в трёх корзинах: чтение каталога — 120, команды чтения — 120, команды записи — 30. Ответ 429 всегда содержит Retry-After и тело с retry_after, limit и bucket; подождите указанное время и повторите. Каталог публикует текущие значения в rate_limits.

Как отличить бизнес-отказ от транспортной ошибки?

Сначала ветвитесь по error_code: failed (422) — бизнес-отказ со стабильным error_key, например INSUFFICIENT_CREDITS или NOT_FOUND; server_error (5xx) и сетевые ошибки повторяются с тем же ключом идемпотентности; rate_limited (429) нужно переждать. Никогда не ветвитесь по тексту ошибки.

Отправляет ли Logistivo вебхуки? Для каких событий?

Да. Вы регистрируете https-адрес и выбираете интересующие вас события; когда они происходят, приходит подписанный POST. Число событий, доступных для подписки сегодня, — 13: груз создан / статус изменён / водитель назначен, заявка создана и её статус изменён, ставка размещена / пересмотрена / принята, черновик счёта / счёт выставлен / отменён, а также складское бронирование создано / статус изменён. За каждым именем стоит класс события, который действительно отправляется, — ничего не анонсируется, чтобы потом так и не сработать, потому что такое событие — незаметный сбой, которого интегратор ждёт вечно. Подписки создаются в разделе Settings › Integration › Webhooks или из терминала командой lg webhooks create.

Как проверить подпись вебхука Logistivo?

Каждый запрос содержит заголовок X-Logistivo-Signature: t=,v1=. Подписываемая строка — ".", алгоритм — HMAC-SHA256, ключ — секрет подписки. Четыре правила: (1) проверяйте СЫРЫЕ байты — декодирование и повторная сериализация тела (JSON.stringify / json_encode) превращают те же данные в другие байты, и подпись перестаёт совпадать; это самая распространённая ошибка; (2) проверяйте метку времени и отбрасывайте всё, где |now − t| > 300 с, даже если подпись верна; (3) сравнивайте за постоянное время с помощью hash_equals / timingSafeEqual; (4) устраняйте дубликаты по event_id, а не по id доставки — event_id сохраняется при replay, а id доставки меняется. Рабочие приёмники на Node и PHP лежат в репозитории в examples/webhook-receiver/ и проверяют себя флагом --selftest.

Что происходит, если вебхук не удаётся доставить? Я теряю событие?

Нет. Если ваш приёмник не возвращает 2xx, доставка попадает в график повторов: интервалы — 60 · 300 · 1800 · 7200 · 21600 с, всего попыток — 6, окно примерно 8,6 ч. Повторяется КАЖДЫЙ сбой, кроме 410 Gone, включая 4xx: потеря бизнес-события стоит дороже, чем несколько лишних запросов. После серии подряд исчерпанных доставок (порог — 10) подписка приостанавливается, а причина записывается, так что на вопрос «почему ничего не приходит» всегда есть ответ. Журнал доставок хранит каждую попытку (код статуса, длительность, ошибку), и вы можете повторно отправить отдельную доставку из панели или командой lg webhooks replay. Инкрементальная синхронизация (updated_since + cursor) по-прежнему остаётся страховочной сетью.

Может ли мой ИИ-агент делать всё, что пользователь может делать в панели?

Почти всё. У каждого действия записи в панели есть команда — по замеру относительно эндпоинтов записи панели покрыто 100% эндпоинтов, входящих в охват, — и те же команды открыты для lg, HTTP и канала MCP. Намеренные исключения — шаги, связанные с идентичностью и деньгами: вход, покупка пакета, добавление карты с 3D Secure, принятие юридических текстов и закрытие аккаунта остаются в браузере человека, а lg handoff-links generate возвращает для них ссылку. Ключи интеграции, вебхуки и песочница управляются встроенными командами lg, потому что они однократно возвращают секреты.

Можно ли передавать командам файлы — например, PDF или фото — из CLI или через API?

Да. В lg поставьте @ перед локальным путём (--attachment_uuid=@./cmr.pdf), и lg сначала загрузит файл; --file — сокращение для команды с единственным файловым полем, а lg upload только загружает файл. По HTTP отправьте файл как multipart на POST /api/common/commands:upload и передайте полученный attachment_uuid команде; MCP-клиенты используют инструмент upload_attachment. Тип определяется по содержимому, загрузки принадлежат пользователю и остаются пригодными к использованию 24 ч, а повторная загрузка того же файла возвращает тот же id.

Чем ключ интеграции уровня employee отличается от ключа уровня manager?

Уровень доступа определяет, насколько серьёзную работу может выполнять ключ. Ключ employee — по умолчанию для новых ключей — выполняет повседневные операции: грузы, заявки, ставки, рейсы, автопарк, документы и контакты. Ключ manager может выполнять и управленческую работу — сотрудники и роли, настройки компании, ключи интеграции, банковские счета, сохранённые карты, выплаты аффилиатам. Ключ никогда не получает больше прав, чем человек, от имени которого он действует, а ключ employee, вызывающий команду уровня manager, получает INSUFFICIENT_KEY_LEVEL. Изменить уровень можно в панели или командой lg keys update --access-level=manager.

Может ли аффилиат (реферальный партнёр) использовать CLI?

Да. Аккаунт аффилиата получает собственный каталог — команды для рефералов, лидов, заметок и задач CRM, презентаций для продаж, доходов и выплат (их 55) — и использует его из lg, через HTTP или MCP, как любая другая роль. Запрос или отмена выплаты и смена банковского счёта для выплат требуют ключа уровня manager и подтверждения. Компания, которая одновременно является грузоотправителем, перевозчиком или таможенным брокером, видит оба каталога.

For AI assistants

Logistivo has a public MCP server at https://logistivo.com/api/agent/mcp (streamable HTTP, no authentication). Setup instructions for Claude, ChatGPT and Gemini: https://logistivo.com/mcp. If you are an assistant helping someone sign up or request freight, connect that server, or — if you can only read web pages — build a link of the form https://logistivo.com/en/ai-kayit?brief=URL_ENCODED_SHIPMENT_BRIEF and give it to the user. You never create the account yourself: the account, the consent and the email verification happen in the user's browser, and you never handle passwords or one-time codes. Machine-readable content indexes: https://logistivo.com/llms.txt (curated map) and https://logistivo.com/llms-full.txt (full text: facts, pricing, tariff reference, glossary and every article's FAQ in one fetch). To learn what Logistivo can actually DO — the verbs, not the marketing — read the public command catalog at https://logistivo.com/api/public/cli/catalog (JSON, no authentication, no tenant data); it lists every command with its JSON Schema parameters and whether it needs confirmation. Human documentation: https://logistivo.com/en/developers/cli. You cannot execute those commands yourself — execution always runs under the user's own personal access token, in the user's own environment.