Электронная транспортная накладная из вашей ТМС — по одному API
Т1-API принимает подписанный титул, доводит его до оператора ЭДО, следит за перевозкой
и присылает события обратно в вашу систему. Оператор, роуминг, форматы ФНС и учётки —
на стороне платформы: контракт для всех операторов один. Теми же вызовами ходят заявки перевозчику и документы экспедитора.
Электронная транспортная накладная (ЭТрН) живёт не в одной системе: грузоотправитель подписывает
титул Т1, перевозчик и водитель — свои титулы, обмен идёт через операторов ЭДО, а стороны сделки
часто сидят у разных операторов. Т1-API убирает эту машинерию из вашего кода.
Один контракт на всех операторов
Контур, СберКорус, Астрал, СБИС. Маршрут выбирает платформа — или вы, кодом оператора.
Контрагента видно заранее
У каких операторов есть ящики и с какими ИдУчЭДО — до подачи, а не отказом после.
Титулы Т1–Т9 целиком
Вся цепочка перевозки, включая переадресовку и замену водителя, в одной карточке накладной.
Песочница по ключу
Тестовый ключ — и тот же боевой URL, но с моками операторов. Полный цикл до delivered за минуты.
Что вам нужно на своей стороне
Титул Т1 в формате ФНС 5.01 (файл в кодировке windows-1251) и отсоединённая
КЭП к нему — CMS/PKCS7. Формировать титул и подписывать вы можете чем угодно: своим СКЗИ,
КриптоПро, сервисом подписи.
API-ключ из веб-кабинета организации.
HTTPS-эндпоинт для вебхуков — если хотите узнавать о событиях сразу, а не поллингом.
Водительская часть цепочки (Т2, Т4 — приём груза и выдача) подписывается в мобильном приложении
«Куб Транспорт» и появляется в карточке накладной автоматически. Вашей интеграции для этого
делать не нужно ничего.
Пять шагов от нуля до первой накладной в песочнице.
1. Выпустите ключ
Веб-кабинет cabinet.etrn.app →
Настройки → API → «Выпустить ключ». Дайте ключу название («1С:Бухгалтерия, прод») —
через полгода это единственный способ понять, какой ключ где работает. Начните с тестового: он
включает песочницу целиком.
🔑
Подставить свой ключ в примеры
Значение подставится во все примеры на странице. Оно остаётся в вашем браузере: ни на сервер,
ни в аналитику не уходит — на этой странице их просто нет.
2. Проверьте ключ одним запросом
GET /organization вернёт организацию, тариф и остаток лимита — заодно убедитесь, что ключ живой.
Соединение = Новый HTTPСоединение("api.etrn.app", 443, , , , 30,
Новый ЗащищенноеСоединениеOpenSSL);
Запрос = Новый HTTPЗапрос("/v1/organization");
Запрос.Заголовки.Вставить("Authorization", "Bearer " + КлючAPI); // etrn_test_…
Ответ = Соединение.Получить(Запрос);
Сообщить(Ответ.ПолучитьТелоКакСтроку());
3. Подготовьте титул
Ядро v1 принимает готовую пару: XML титула Т1 (ФНС 5.01, windows-1251) и отсоединённую подпись
CMS/PKCS7. Оба передаются строками base64 — кодируйте исходные байты файлов как есть.
Если ваше СКЗИ уже отдаёт подпись base64-текстом, не кодируйте её второй раз.
4. Прогоните проверку без подачи
POST /waybills/validate — тот же конвейер проверок, что и при подаче, но ничего не
создаётся и не списывается. Удобно ставить в CI титулогенератора.
POST /v1/waybills/validate
jq -n --arg xml "$XML_B64" --arg sig "$SIG_B64"'{
signed_xml: {xml: $xml, signature: $sig},
parties: [
{role: "shipper", inn: "1650152978", kpp: "165001001"},
{role: "carrier", inn: "7713488175"},
{role: "consignee", inn: "7736207543"}
]
}' | curl -sS https://api.etrn.app/v1/waybills/validate \
-H "Authorization: Bearer $ETRN_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @-
# → {"valid": true, "problems": []}# либо valid=false и problems[{field, message}] — что именно не сошлось
5. Подайте накладную и прогоните цикл
Дальше — подача и, в песочнице,
simulate: накладная за минуты доходит до delivered,
в цепочке появляются подписанные Т2 и Т4, а ваш обработчик вебхуков получает настоящие события.
Аутентификация — заголовок Authorization: Bearer <ключ> в каждом запросе.
Базовый URL — https://api.etrn.app/v1, только HTTPS. Версия живёт в базовом пути;
в самих ручках префикса нет.
Ключ принадлежит организации, а не сотруднику: увольнение человека не гасит
интеграцию. Значение показывается один раз при выпуске — платформа хранит только его хэш,
«показать ещё раз» невозможно by design.
🔄
Ротация без простоя
Выпустите второй ключ → переключите на него интеграцию → отзовите старый. Оба ключа живут
параллельно, пока вы переключаетесь. Обратный порядок («отозвать, потом выпустить»)
останавливает обмен.
Контур ключа виден в самом значении: etrn_live_… — боевой, etrn_test_… —
песочница. Один и тот же код работает с обоими: меняется только ключ.
Тестовый ключ включает песочницу целиком — отдельного стенда, адреса и настроек не нужно.
Запросы идут на тот же https://api.etrn.app/v1, но обслуживаются управляемыми
моками операторов: оператор sandbox подставляется сам, поле operator
можно не передавать.
POST /waybills/{waybillId}/simulate продвигает накладную по жизненному циклу без
реального водителя и оператора — включая появление подписанных Т2/Т4 в documents[].
POST /orders/{id}/simulate и POST /forwarding-orders/{id}/simulate
изображают ответ контрагента на заявку или поручение (accepted, для заявок ещё и
declined с причиной): настоящий титул Т2, УИД ГИС ЭПД, статус и вебхуки
order.*/forwarding.* — как в бою.
Вебхуки приходят по-настоящему: обработчик отлаживается на полной цепочке событий.
Ни один боевой документ не расходуется и ни один рубль не списывается.
⚠️
Подписка на вебхуки — одна на оба контура
Вебхук заводится на юрлицо, а не на ключ, поэтому один и тот же URL получает события
и песочницы, и боевого контура. Это удобно при отладке, но означает, что ваш прод-обработчик
может получить тестовое событие.
Отличать их просто: у песочных событий operator равен
"sandbox", у боевых — код реального оператора (kontur,
astral, sbis, sberkorus). Проверяйте это поле
перед тем, как заводить документ у себя. Либо на время отладки указывайте отдельный URL
и удаляйте подписку после.
POST /v1/waybills/{waybillId}/simulate
# Прогнать цепочку до конца — события придут в вебхук как в бою
curl -sS "https://api.etrn.app/v1/waybills/$WAYBILL_ID/simulate" \
-H "Authorization: Bearer $ETRN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"advance_to": "delivered"}'
advance_to принимает любой статус жизненного цикла; переход назад невозможен (409).
В боевом контуре ручки simulate нет вовсе — с ключом etrn_live_… она
отвечает 404.
Ответ — документ целиком, как в GET /orders/{id}: статус accepted или
declined, Т2 в titles[], УИД в gis_uid. Доступно отправителю
документа со status: sent; повтор идемпотентен. Для поручений поддерживается только
accepted: отказа экспедитора в формате ФНС нет.
Интеграция с ЭДО ломается не на счастливом пути, а на обрывах: оператор задумался, сеть моргнула,
ваш скрипт перезапустился и послал накладную второй раз. Ниже — что платформа обещает в каждом из
этих случаев. Всё это проверяется нашими автотестами и живым мониторингом, а не только описано здесь.
Повторная подача одной и той же накладной никогда не создаёт второй документ у оператора — на двух
независимых уровнях:
Уровень
Как работает
Зачем
Idempotency-Key
Заголовок до 128 символов, окно 24 часа. Повтор с тем же ключом возвращает исходный ответ (200 вместо 201). Тот же ключ с другим телом — 409 idempotency-conflict.
Классический сетевой ретрай: ответ не дошёл, вы повторили запрос.
ИдФайл титула
Идентификатор файла по правилу ФНС внутри XML уникален для каждого титула. Платформа ведёт по нему журнал подач бессрочно: тот же титул, поданный любым способом и с любым ключом, возвращает уже созданную накладную.
Защита от дублей, когда ретрай сделала не ваша библиотека, а человек или соседняя система.
Подача синхронная: обычно ответ приходит за доли секунды и не позже чем через 25 секунд после того,
как платформа взялась за подачу, — в этот срок входит и оформление лицензии оператора. Если к сроку
запрос оператору ещё не отправлен, титул не отправляется вовсе: 503 operator-throttled,
повтор безопасен. Если запрос ушёл, а ответа оператора нет, вы получаете 409 conflict с
reason: submission_pending — но подача на этом не заканчивается: платформа продолжает её
в фоне до полутора минут и фиксирует результат. 504 upstream-timeout — только если не
ответила сама платформа (шлюз ждёт её 30 секунд).
Что вы получили
Что произошло
Что делать
201 / 200
накладная у оператора, id в ответе
ничего — дальше события и карточка
504 upstream-timeout или обрыв с вашей стороны
подача могла начаться; начатая идёт в фоне
повторите тот же запрос через пару минут: завершённая подача вернёт 201 с той же накладной, не принятая оператором — уйдёт заново, а пока исход неизвестен — 409 с reason: submission_pending. Второго документа не будет
409 conflict, reason: submission_pending
подача ушла оператору, а ответа нет: не пришёл, оборвался или не разбирается, пришла ошибка 500/502/504 или оператор не уложился в срок ответа — исход неизвестен. Так же отвечает повтор, пока первая подача в пути
не повторяйте в цикле и не пересоздавайте титул с новым ИдФайл: повтор заблокирован, чтобы у оператора не появился второй документ. Подачи в Контур платформа сверяет сама — см. как. Если через пару часов ничего не изменилось, пришлите request_id в поддержку
424 operator-*
оператор ждёт действия клиента (вход, права, включение API)
до действия ретрай бесполезен: см. каталог ошибок, сообщите клиенту, что именно сделать. После — повторите тот же титул, он уйдёт оператору заново
503 operator-throttled
документа у оператора нет: запрос не дошёл до него (или отправка не началась к сроку ответа — например, пока оформлялась лицензия), либо оператор явно отказал (429, 503)
повторите тот же запрос через Retry-After (сейчас 120 секунд) — титул уйдёт оператору заново
503 operator-disabled
платформа отключила оператора (обслуживание, авария) — титул к нему не уходил
Retry-After нет, срок знает администратор: выберите другого оператора из GET /operators или повторите, когда этот вернётся
422 validation-failed
титул или подпись не прошли проверку — оператору ничего не уходило
исправьте по errors[] и подайте заново, это новый документ
🔁
Правило одно: повторяйте тот же документ смело
Завершённая подача отдаёт свою накладную любому повтору того же титула без второй отправки — и точному повтору запроса, и титулу, подписанному заново. Если оператор не взял титул в работу — недоступен или перегружен (503 operator-throttled) либо ждёт действия клиента (424), — бронь снимается и повтор того же титула уходит оператору заново. Версию, которую оператор отклонил по существу, платформа повторно не отправляет: 409 с reason: submission_rejected, нужна новая подпись. Пока исход подачи неизвестен (запрос ушёл, ответа нет), повтор блокируется: 409 с reason: submission_pending — второй документ у оператора хуже ожидания. Для Контура такой исход платформа выясняет сама: находит документ — накладная появляется, убеждается, что его нет, — снимает блок, но не раньше чем через час после неудачной подачи.
Три вещи, на которых интеграции спотыкались в бою, платформа принимает и приводит к норме сама —
документ доходит до оператора в том виде, который тот примет:
Особенность
Что делаем
Почему это важно
Подпись в BER (потоковый КриптоПро, неопределённые длины)
перекодируем в строгий DER, байты подписи не трогаем
Контур отвечает на BER «CannotParseSignature», хотя подпись математически верна
Подпись закодирована base64 дважды
снимаем лишний слой
типичная ошибка при склейке ответа СКЗИ с JSON
Пробелы и переносы строк внутри base64
убираем
так печатают подпись многие СКЗИ и 1С
Чего платформа не чинит: кодировку титула (файл должен быть в windows-1251), содержимое
XML и сертификат подписанта. Эти проблемы возвращаются как 422 с адресом поля в errors[].
Каждое событие доставляется хотя бы один раз. Обработчик обязан быть идемпотентным по
event.id — повтор возможен.
Порядок между событиями не гарантируется: два статуса могут приехать в обратной очерёдности при
ретраях. Сверяйтесь с data.waybill.status и updated_at, а не с порядком
получения.
Ретраи: 8 попыток с паузами 1 мин → 5 мин → 15 мин → 1 ч → 3 ч → 6 ч → 12 ч — почти сутки на то,
чтобы поднять ваш сервер. После восьмой неудачи подписка помечается broken;
POST /webhooks/{id}/ping возвращает её в строй без пересоздания.
На ответ вашего сервера отводится 10 секунд. Отвечайте 2xx сразу, обрабатывайте потом.
Внутри v1 изменения только аддитивные: поля добавляются, существующие не меняют смысл и не
исчезают. Незнакомые поля и типы событий ваш код должен пропускать молча.
Ломающее изменение — это новый базовый путь /v2; v1 живёт до объявленного заката,
о котором мы пишем в журнале изменений и на контакт организации заранее.
Каждая 5xx в API поднимает дежурного: у нас алерт на любую серверную ошибку в этом контуре.
В ответе всегда есть request_id — по нему запрос находится целиком, вместе с ответом
оператора.
POST /v1/waybills
принимает подписанный титул Т1 и стороны сделки. Ответ — объект Waybill:
201, если накладная принята, 200 — если это повтор по
Idempotency-Key.
Поле operator необязательно: не передали — оператора выберет платформа по правилу
организации. Хотите управлять сами — передайте код из GET /operators;
неизвестный или выключенный код отклоняется с 422.
Передавайте Idempotency-Key при каждой подаче. Повтор с тем же ключом в течение
24 часов вернёт исходный результат (200 вместо 201) — сеть, таймаут и рестарт вашей очереди
не превратятся во вторую накладную. Тот же ключ с другим телом — 409.
Подпись титула: требования и типичные причины 422 #
Платформа принимает готовую пару «XML титула + отсоединённая подпись». Ниже — что должно быть на
входе, чтобы титул прошёл и нашу проверку, и оператора. Каждый пункт — из живых интеграций.
Требование
Как правильно
Формат титула
ЭТрН по формату ФНС 5.01, титул Т1 грузоотправителя. Файл в кодировке windows-1251, как требует формат.
ИдФайл
Строится по правилу ФНС и уникален для каждого титула. Он же — ключ идемпотентности: новый документ = новый ИдФайл.
ИНН отправителя
ИНН в титуле должен совпадать с parties[role=shipper].inn и принадлежать организации ключа (или юрлицу группы).
Подпись
Отсоединённая КЭП в контейнере CMS/PKCS#7, ГОСТ Р 34.10-2012. Предпочтителен DER; BER принимается и перекодируется.
Сертификат
Квалифицированный сертификат организации-отправителя. Если подписывает представитель по МЧД — реквизиты доверенности должны стоять в титуле (блок СвДовер).
Кодирование
Оба поля — base64 от исходных байтов файлов. Если СКЗИ уже отдало base64, не кодируйте второй раз (мы это распознаём, но лучше не полагаться).
signed_xml.signature — «ИНН подписанта не совпадает с отправителем»
подписано сертификатом другой организации или сотрудника без МЧД
сертификат юрлица-отправителя, либо представитель с МЧД и блоком СвДовер в титуле
operator — «оператор неизвестен или выключен»
код не из GET /operators или отключён
не передавайте поле вовсе — платформа выберет сама
errors[] пуст, title — «Оператор ЭДО отклонил документ»
титул дошёл до оператора, и он отказал по существу: у плательщика закончился пакет, поля титула, подпись, титул адресован другому оператору
причина и что сделать — в detail, его можно показать человеку как есть; исправьте и подайте новой подписью
🧪
Проверяйте в CI, а не на бою
POST /waybills/validate гоняет ровно тот же конвейер, что и подача, включая криптопроверку подписи, но ничего не создаёт и не списывает. Поставьте его в тесты титулогенератора: ошибка формата будет видна до того, как её увидит клиент.
накладная принята платформой, но ещё не дошла до pending_receipt. В ответе на подачу не встречается: недоступный или перегруженный оператор — это 503 operator-throttled, повтор за вами
pending_receipt
титул у оператора, ждём приёма груза водителем (Т2)
in_transit
груз принят, машина в пути
pending_delivery
прибытие к грузополучателю, ждём выдачи (Т4)
delivered
перевозка завершена, цепочка титулов закрыта
failed
терминальный: причина в failure {code, message}, списанная подпись возвращается — исправленный титул подаётся новой накладной
cancelled
стороны аннулировали поток на стороне оператора
При обычной синхронной подаче submitting вы не увидите — ответ сразу
pending_receipt. Статус awaiting_signature относится к версии v1.1
(черновик + двухфазная подпись) и в ядре не встречается.
Все титулы перевозки приезжают в documents[] карточки накладной — с XML, подписью и
временем. Вам не нужно знать, какой оператор их принёс и в каком формате они лежали у него.
Титул
Кто подписывает
Когда появляется
T1
грузоотправитель
ваша подача — начало цепочки
T2
водитель (приём груза)
погрузка; в приложении «Куб Транспорт»
T3
грузоотправитель
подтверждение приёма груза к перевозке
T4
водитель (выдача груза)
выгрузка у грузополучателя
T5
грузополучатель
приём груза получателем
T6
грузоотправитель / перевозчик
уточнение условий перевозки
T7
перевозчик
переадресовка — смена пункта выгрузки
T8
перевозчик
замена водителя или транспортного средства
T9
стороны
служебные отметки цепочки
✍️
Подписывать Т2–Т9 из API не нужно
Водительские титулы подписываются в мобильном приложении, титулы контрагентов — у них.
Ваша интеграция подаёт Т1 и читает остальное: подписка на
waybill.document_added сообщит о каждом новом документе цепочки.
🆔
УИД в ГИС ЭПД приходит позже — и это нормально
Накладная получает УИД не в момент подачи: Т1 уходит оператору без него,
а идентификатор присваивает государство. Поэтому в ответе на создание
поле gis_uid пустое, и ждать его не нужно — подавайте
следующие накладные сразу, хоть тысячу подряд.
Когда УИД назначен, приходит событие waybill.gis_uid_assigned
с готовым значением в data.waybill.gis_uid. Тот же УИД потом
виден в карточке накладной. Он один на всю цепочку Т1→Т2→Т3→Т4, так что
по нему удобно связывать документы у себя.
Контракт один, но за ним четыре оператора с разным характером. Платформа скрывает различия, где это
возможно; здесь — то, что всё же полезно знать интегратору, чтобы читать ответы и объяснять клиентам.
Оператор
Код
Идентификатор абонента
Особенности
Контур (Диадок, Логистика)
kontur
2BM-…
Клиент авторизует свой кабинет Контура в нашем кабинете один раз. Если доступ отозван (смена пароля, вход с другого устройства), подача отвечает 424 operator-reauth-required — клиенту нужно войти заново. Подписать титул может только участник накладной: ИНН и ящик отправителя в титуле должны быть его.
СберКорус
sberkorus
2BK-…
Подключение оформляется письмом в отдел настройки оператора, платформа делает это за клиента. Т2 и Т4 водителей приходят через несколько секунд после подписания в приложении.
Астрал (Калуга Астрал)
astral
2AE… (GUID)
Получатели документа берутся из ИдФайл титула, если вы не передали operator_subscriber_id в parties. Ответ оператора приходит событием и может занять до минуты — это тот случай, когда бывает 504 и работает повтор без дублей.
СБИС (Тензор)
sbis
2BE…
Обмен идёт от аккаунта клиента, которому наша организация должна быть выдана в правах. Права отозваны — 424 operator-access-revoked; обмен по API не включён у оператора — 424 operator-api-not-enabled.
participant_id в GET /organization и GET /counterparties/{inn} — это
идентификатор участника ЭДО по ФНС: первые три символа обозначают оператора (2AE Астрал,
2BM Контур, 2BK СберКорус, 2BE Тензор, 2AL Такском
и так далее — распознаётся весь действующий реестр операторов ФНС, а не только те четыре,
через которых возит платформа). У организации может быть несколько идентификаторов — по одному
у каждого оператора, а иногда и несколько ящиков у одного. Все они приходят в
participant_ids, а с именем оператора и признаком «наш» — в массиве
operators. Этот же идентификатор стоит в ИдФайл титула.
GET /counterparties/{inn} отвечает заранее: найдена ли организация в ЭДО, у каких
операторов и с какими идентификаторами. Проверяйте перевозчика и грузополучателя до подачи.
reachable: true означает «организация найдена в реестре ЭДО», а не «документ
до неё дойдёт»: наличие ИдУчЭДО не доказывает ни того, что у контрагента подключён именно
ЭПД, ни того, что роуминг с его оператором работает в продуктивной среде. Между Контуром,
Астралом, Тензором и СберКорусом ЭТрН в роуминге работает; за их пределами — уточняйте у
контрагента, какой ящик принимает ЭТрН.
🔌
Связь с оператором может пропасть без вашего участия
Токен клиента у оператора может протухнуть, права — быть отозваны. Платформа пишет клиенту письмо с точным действием в момент потери связи и второе — когда обмен восстановлен. Вам как интегратору приходит честный 424 с кодом причины: покажите его текст клиенту, а не повторяйте запрос по расписанию.
GET /v1/waybills/{waybillId}
отдаёт карточку с массивом documents[]. Содержимое каждого документа скачивается
отдельно — XML или отсоединённая подпись:
Скачать XML титула и его подпись
# id из ответа на подачу — наш UUID накладной (он же waybillId в путях). Это НЕ УИД_ТрН:
# УИД_ТрН придёт в карточке полем gis_uid, когда оператор зарегистрирует документ в ГИС ЭПД
WAYBILL_ID=$(jq -r .id waybill.json)
# Карточка: статус + цепочка документов (Т2/Т4 водителя появятся здесь же)
curl -sS "https://api.etrn.app/v1/waybills/$WAYBILL_ID" \
-H "Authorization: Bearer $ETRN_API_KEY" -o card.json
# Ид документа цепочки — из карточки: documents[].id; вид — documents[].kind (T1, T2, T4 …)
DOC_ID=$(jq -r '.documents[] | select(.kind=="T2") | .id' card.json | head -1)
# XML документа цепочки и отсоединённая подпись к нему
curl -sS "https://api.etrn.app/v1/waybills/$WAYBILL_ID/documents/$DOC_ID/content?part=xml" \
-H "Authorization: Bearer $ETRN_API_KEY" -o t2.xml
curl -sS "https://api.etrn.app/v1/waybills/$WAYBILL_ID/documents/$DOC_ID/content?part=sig" \
-H "Authorization: Bearer $ETRN_API_KEY" -o t2.xml.sig
# QR подтверждения перевозки — показать инспектору на дороге (PNG)
curl -sS "https://api.etrn.app/v1/waybills/$WAYBILL_ID/qr" \
-H "Authorization: Bearer $ETRN_API_KEY" -o qr.png
🧭
Какой идентификатор для чего
id — наш UUID накладной: выдаётся сразу при подаче, не меняется, стоит в путях как
waybillId и в вебхуках.
gis_uid — УИД_ТрН в ГИС ЭПД: его присваивает оператор при регистрации Т1, поэтому в
ответе на POST его ещё нет; появляется в карточке и в вебхуке и дальше постоянен для
Т1–Т4 — им сверяются с ГИС ЭПД и с ЭДО контрагента.
documents[].id — ид документа цепочки, только для скачивания content.
operator_waybill_id — ид перевозки у оператора ЭДО, для сверки на его стороне.
external_ref — ваш номер рейса из ТМС, по нему есть фильтр в списке.
До накладной есть ещё документы. Заказчик присылает перевозчику заявку, перевозчик соглашается
или отказывает. Клиент поручает груз экспедитору, экспедитор подтверждает приём расписка.
С 12 октября 2026 эти документы ходят по тому же API, что ЭТрН, и по тем же принципам: один
контракт для всех операторов ЭДО, роуминг и форматы ФНС — на стороне платформы. Отличие одно,
и оно в вашу пользу: титул собираем мы. Вы передаёте форму в JSON, платформа строит
XML по формату ФНС, проверяет его и отдаёт байты на подпись.
Документ
Кто → кому
Титулы
Базовый путь
Заявка на перевозку (ЭЗЗ)
заказчик — грузоотправитель или экспедитор → перевозчик
Декодируйте title.xml из base64 и подписывайте ровно эти байты отсоединённой КЭП
(CAdES-BES, как для накладной — см. требования к подписи).
title.sha256 — чтобы сверить, что подписали то, что собрано. Поправили форму —
соберите титул заново: старая подпись к новым байтам не подойдёт, подача ответит
409 title-changed.
Ключи и доступ — те же. Для заявок нужен скоуп orders:read / orders:write,
для экспедиции — forwarding:read / forwarding:write; ключи со скоупами
waybills:* проходят без перевыпуска. Сторона документа действует от юрлица ключа
(у группового — от любого подтверждённого участника, укажите org_inn);
право подписи за юрлицо даёт его подтверждённая связка с оператором ЭДО. Тарификация — по подписям:
каждый поданный титул списывает одну подпись у организации, которая его подписала.
🧪
Песочница: тестовый ключ подаёт в sandbox
С тестовым etrn_test_-ключом документ уходит мок-оператору sandbox
(operator подставляется сам), подписи не списываются, после подачи документ получает
status: sent. Так проверяется вся ваша сторона: форма, сборка титула, подпись байтов,
подача. Ответ контрагента изображает POST /v1/orders/{id}/simulate
(для поручений — POST /v1/forwarding-orders/{id}/simulate): платформа собирает
настоящий титул Т2 от лица контрагента, документ получает УИД ГИС ЭПД и статус
accepted или declined, вебхуки и цепочка — как в бою.
Боевые документы сейчас ходят через Астрал и СберКорус; СБИС и Контур — следующим этапом.
Три вызова. POST /v1/orders сохраняет
черновик по вашей форме и сразу собирает титул Т1: в ответе document, title.xml
(base64), title.sha256 и title.prepared_title_id. Вы подписываете XML и отдаёте
подпись в POST /v1/orders/{id}/titles/t1/submit. Документ уходит оператору ЭДО и дальше живёт
сам: awaiting_operator → sent → accepted или declined.
# 1. Черновик + титул Т1 на подпись. Idempotency-Key защищает от сетевого повтора,
# external_ref в форме — от второго документа по той же заявке ТМС (409 external-ref-taken)
curl -sS -X POST https://api.etrn.app/v1/orders \
-H "Authorization: Bearer $ETRN_API_KEY" \
-H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" \
-d @order.json -o created.json
# 422? Черновик всё равно сохранён: .document_id, поля — .errors[]. Поправьте PATCH-ем и соберите …/send
ORDER_ID=$(jq -r .document.id created.json)
jq -r .title.xml created.json | base64 -d > t1.xml
jq -r .title.sha256 created.json; shasum -a 256 t1.xml # должны совпасть# 2. Подпись — отсоединённая CAdES-BES вашим СКЗИ сертификатом организации-заказчика → t1.xml.sig# 3. Подача: документ уходит оператору ЭДО перевозчика
curl -sS -X POST "https://api.etrn.app/v1/orders/$ORDER_ID/titles/t1/submit" \
-H "Authorization: Bearer $ETRN_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"signature\": \"$(base64 < t1.xml.sig | tr -d '\n')\"}"# Статус и ответ перевозчика — в карточке: .status, .stage, .decline.reason
curl -sS "https://api.etrn.app/v1/orders/$ORDER_ID" -H "Authorization: Bearer $ETRN_API_KEY"
import base64, json, uuid, requests
BASE_URL = "https://api.etrn.app/v1"
API_KEY = "etrn_test_…"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {API_KEY}"with open("order.json", encoding="utf-8") as f:
order = json.load(f)
r = session.post(f"{BASE_URL}/orders", json=order, headers={"Idempotency-Key": str(uuid.uuid4())})
if r.status_code == 422:
problem = r.json() # черновик сохранён: problem["document_id"]; что поправить — problem["errors"]raise SystemExit(problem["errors"])
if r.status_code == 409 and r.json().get("existing_id"):
raise SystemExit(f"уже есть: {r.json()['existing_id']}")
r.raise_for_status()
created = r.json()
xml_bytes = base64.b64decode(created["title"]["xml"])
signature = sign_detached(xml_bytes) # ваше СКЗИ: отсоединённая CAdES-BES, DER
doc_id = created["document"]["id"]
r = session.post(f"{BASE_URL}/orders/{doc_id}/titles/t1/submit", json={
"signature": base64.b64encode(signature).decode(),
"prepared_title_id": created["title"]["prepared_title_id"],
})
r.raise_for_status()
print(r.json()["document"]["status"]) # awaiting_operator → sent → accepted | declined
🧩
Что обязательно в форме — подскажет сборщик
Правила формата ФНС зашиты в платформу: пропущенное или неверное поле вернётся в
errors[] (422) или в problems[] карточки с путём в snake_case —
cargo[0].gross_kg, vehicle.type, loading_at. Числа передаются
строками с точкой, как в XML; даты — YYYY-MM-DD; loading_at — с часовым
поясом. Для function: "Заявка" нужен договор об организации перевозок
(contract). Полные схемы форм — в OpenAPI.
Входящие заявки — GET /v1/orders?direction=incoming&stage=your_turn. Принять —
POST /v1/orders/{id}/accept с водителями, транспортом и ценой; отклонить —
…/decline с причиной. В обоих случаях в ответе титул Т2 на подпись, подача —
…/titles/t2/submit. Заказчик увидит status: accepted или declined
и причину в decline.reason; у него же появится available_actions для следующего шага.
После согласия заказчик выписывает накладную. POST /v1/orders/{id}/waybill-form заводит черновик
ЭТрН и переносит в него груз, стороны, водителя и транспорт из заявки и согласия — в ответе форма черновика и
problems[], чего не хватает; черновик виден и в веб-кабинете. Накладную, поданную через
POST /waybills как обычно, свяжите с заявкой: POST /v1/orders/{id}/link-waybill
с waybill_id — связь появится в links обеих карточек и в цепочке.
Клиент создаёт поручение — POST /v1/forwarding-orders (титул Т1), экспедитор принимает —
…/accept (Т2). Отказа от поручения в формате ФНС нет: вместо него клиент отзывает
поручение — …/revoke (Т3) с датой, причиной и суммой фактических расходов экспедитора.
По принятому поручению экспедитор одним вызовом заводит следующий документ «на основании»:
расписку клиенту — POST /v1/forwarding-orders/{id}/receipt, заявку перевозчику —
…/order, поручение следующему экспедитору — …/forward. Стороны, договор, груз,
адреса и УИД исходного поручения уже перенесены; допишите недостающее PATCH-ем и соберите титул
POST …/{id}/send, дальше подпись и подача как обычно.
🏷️
Признак экспедитора
Получатель поручения должен быть экспедитором — это требование формата, и оператор ЭДО его проверяет.
Признак ставится в профиле организации в веб-кабинете; без него ответ на поручение отвечает
409 forwarder-required. Заявке перевозчику от экспедитора нужен
customer_role: "forwarder" — у документа «на основании» он уже стоит.
Расписка бывает двух видов. Экспедиторская (ЭЭР) — груз принят во владение для перевозки.
Складская (ЭСР) — груз принят на хранение: в форме дополнительно склад с адресом и владельцем
(если склад не экспедитора), условия приёма — дата и время, срок хранения, состояние груза, тары, упаковки, маркировки
и пломб — и кто фактически принял груз, с документом о его полномочиях. Оба вида живут в одном ресурсе
/v1/forwarding-receipts: при создании передайте kind: "warehouse_receipt", по поручению —
POST …/receipt с телом {"kind":"warehouse_receipt"}. Список отдаёт оба вида,
?kind=warehouse_receipt сужает до складских.
По правилам обмена экспедиторскими документами менять их по своей инициативе нельзя. Исправленный файл формируют
только по запросу ГИС ЭПД: если она нашла расхождения, оператор ЭДО сообщает об этом, и исправление нужно отправить
не позже пяти календарных дней. В API такая расписка приходит в статусе failed с УИД и
причиной отказа. POST /v1/forwarding-receipts/{id}/correct заводит черновик того же документа с блоком
correction (ИспрФО, УИД прежний): форма скопирована из подписанного титула. От вас — поправить то, на что
указала ГИС, задать correction.date через PATCH, затем POST …/send, подпись и
подача как обычно.
исправление: черновик → правки → титул
# 1. черновик исправления расписки, отклонённой ГИС ЭПД
curl -sS -X POST https://api.etrn.app/v1/forwarding-receipts/$RECEIPT_ID/correct \
-H "Authorization: Bearer $ETRN_API_KEY" -o fix.json # 201, document.id — исправление# 2. правки по замечанию ГИС и дата исправления
curl -sS -X PATCH https://api.etrn.app/v1/forwarding-receipts/$FIX_ID \
-H "Authorization: Bearer $ETRN_API_KEY" -H "Content-Type: application/json" \
-d '{ "form": { "correction": { "date": "2026-10-14" }, "storage": { "cargo_condition": "Повреждена упаковка двух мест" } } }'
# 3. титул на подпись — и дальше titles/t1/submit, как у первой версии
curl -sS -X POST https://api.etrn.app/v1/forwarding-receipts/$FIX_ID/send \
-H "Authorization: Bearer $ETRN_API_KEY" -H "Content-Type: application/json" \
-d '{ "signer": { "full_name": "Петров Пётр Петрович", "position": "Директор склада" } }'
Доставленную или принятую расписку (sent / accepted) заменить по своей инициативе нельзя:
…/correct отвечает 409 receipt-replacement-off. Формат ФНС (приказ ЕД-1-26/277@) замену
предусматривает (УИД_ПредЭкспР / УИД_ПредСкладР), но платформа её не выписывает, пока не
подтверждено основание.
Исправляется только последняя версия: повторный …/correct до отправки возвращает тот же черновик, а по
уже исправленной расписке отвечает 409 action-not-allowed — работайте с новой.
Расписка без УИД (оператор ещё не зарегистрировал её в ГИС) — 409 gis-uid-pending: дождитесь события
forwarding.gis_uid_assigned.
В GET /v1/chain исправление связано с исходной распиской отношением corrects. Если контрагент
сам выписал замену своей расписки, она придёт отдельным документом по тому же поручению: связь
replaces, в form стоит replaces_gis_uid, прежняя расписка остаётся в истории.
Каждое исправление — отдельный титул и отдельная подпись, тарифицируется как расписка.
GET /v1/chain собирает всё, что связано с одним
звеном: передайте ровно один параметр — order_id, forwarding_order_id,
forwarding_receipt_id или waybill_id. В ответе documents[],
links[] (relation: receipt — поручение → расписка, order —
поручение → заявка, transportation — заявка → накладная) и waybills[] со статусом
перевозки. Чужие звенья, где ваше юрлицо не сторона, в цепочку не попадают.
status — что с документом в целом, stage — чей сейчас ход с вашей точки зрения.
Очередь «что требует действия» — это GET …?stage=your_turn; action_since говорит,
с какого момента ход за вами, available_actions[] — что именно можно сделать.
status
Что это
stage у отправителя / получателя
draft
черновик, титул не собран
draft / —
prepared
титул собран и ждёт подписи
your_turn / —
awaiting_operator
подпись принята, оператор ЭДО обрабатывает
waiting / —
failed
оператор отклонил титул: причина в titles[].failure, поправьте и соберите заново
your_turn / —
sent
документ у получателя, ждёт его ответа
waiting / your_turn
accepted
получатель согласился (Т2 с согласием)
done / done
declined
перевозчик отказал (Т2 с отказом), причина в decline.reason
declined / declined
revoked
клиент отозвал поручение (Т3)
revoked / revoked
О движении заявок и экспедиторских документов сообщают вебхукиorder.* и forwarding.*: документ приходит в теле события в той же форме, что
и в карточке, глазами вашей стороны. Ошибки этих ручек описаны в общей
таблице ошибок.
Подписка — POST /webhooks с url (только https) и списком событий.
Пустой список означает «все события». secret возвращается один раз
в ответе на создание: список подписок секретов не отдаёт. Утеряли — удалите подписку и создайте
новую; это же и штатная ротация секрета.
в цепочке новый документ, например подписанный Т2 или Т4 (data.document)
waybill.gis_uid_assigned
ГИС ЭПД присвоила накладной УИД — он приходит в data.waybill.gis_uid
order.created forwarding.created
документ появился у вас: отправителю — при создании (в том числе черновик из кабинета), получателю — когда документ до него дошёл (status: sent, stage: your_turn). Документ — в data.order / data.forwarding_document, той же формы, что карточка, с титулами в titles[]
order.status_changed forwarding.status_changed
переход, который не является ответом на ваш же запрос: sent, accepted, declined, revoked, failed (последнее — только отправителю); прежний статус — в data.previous_status. По prepared и awaiting_operator событий нет — их вы получаете в ответе API
У документа две стороны: отправитель и получатель получают одно событие (общий id), каждый в
своём виде — direction, stage, available_actions считаются для вашей
стороны. Песочница (operator: sandbox) до второй стороны не доходит. Подписка хранит список
событий, зафиксированный при создании: созданная до появления order.* и
forwarding.* их не получает — пересоздайте её с нужным списком или пустым («все события»).
POST /webhooks/{id}/ping отправляет на ваш адрес тестовое событие
прямо сейчас и возвращает то, что ответил ваш сервер: код, время,
причину неуспеха. Тело и подпись — как у боевых событий, поэтому проверяются и приём,
и разбор, и сверка X-Etrn-Signature. Тип события — ping:
он не входит в список подписки, так что заодно убедитесь, что незнакомые типы ваш
код пропускает молча.
POST /v1/webhooks/{id}/ping
curl -sS -X POST https://api.etrn.app/v1/webhooks/$WEBHOOK_ID/ping \
-H "Authorization: Bearer $ETRN_API_KEY"# → 200 — мы позвали ваш адрес. Результат смотрите в полях:
{
"delivered": false,
"status_code": 502,
"duration_ms": 88,
"event_id": "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"error": "сервер ответил 502 — вебхук считается доставленным только на 2xx",
"webhook_status": "active",
"reactivated": false
}
HTTP 200 — это «мы позвали», а не «всё хорошо». Ошибка вашего сервера
приходит в delivered и status_code: так видно, чей именно
сервер отказал.
status_code: 0 — ответа не было вовсе: сеть, таймаут (ждём 8 секунд),
TLS или адрес не прошёл проверку.
Пинг ничего не ломает. Попытки не растут, неудачный пинг не убивает подписку.
Оживляет сломанную подписку. Если status: broken — почините свой
обработчик и вызовите ping: успешный ответ снимает broken
(reactivated: true), и события идут дальше. Пересоздавать подписку и менять
secret не нужно.
Правила доставки
At-least-once. Одно событие может прийти повторно — обработчик обязан быть
идемпотентным по event.id.
Ретраи. Любой ответ не-2xx повторяется с растущей паузой до 24 часов.
После суток неудач подписка помечается сломанной (status: broken) и видна такой
в кабинете и в GET /webhooks. Вернуть в строй — ping (см. выше).
Порядок не гарантируется. Ориентируйтесь на created_at события и
текущий status накладной, а не на порядок приходов.
Без вебхуков тоже можно. Поллинг
GET /waybills?updated_from=… — законная стратегия для простых
интеграций. Берите updated_from, а не created_from:
накладная, поданная вчера, сегодня меняет статус, и в окно СОЗДАНИЯ такое
изменение не попадёт. Запоминайте наибольший updated_at из
прошлой выдачи и передавайте его в следующий запрос.
Каждая доставка подписана заголовком X-Etrn-Signature: t=<unix>,v1=<hex(hmac_sha256(secret, t + "." + body))>
Проверяйте подпись до разбора тела и отбрасывайте метки времени старше пяти минут —
это защита от повторной отправки перехваченного запроса. Ниже — готовые функции.
"""Приём вебхука eTRN: проверка подписи и идемпотентная обработка."""import hashlib, hmac, json, time
WEBHOOK_SECRET = "…"# из ответа POST /webhooks — показывается один раз
REPLAY_WINDOW_SECONDS = 300# 5 минутdef verify_signature(secret: str, header: str, body: bytes) -> bool:
"""X-Etrn-Signature: t=<unix>,v1=<hex(hmac_sha256(secret, t + "." + body))>."""try:
parts = dict(p.strip().split("=", 1) for p in header.split(","))
t, their = parts["t"], parts["v1"]
except (ValueError, KeyError):
returnFalseifnot t.isdigit() orabs(time.time() - int(t)) > REPLAY_WINDOW_SECONDS:
returnFalse# метка времени вне окна — возможен replay
ours = hmac.new(secret.encode(), (t + ".").encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(ours, their) # сравнение за постоянное времяdef handle_webhook(headers: dict, body: bytes) -> int:
ifnot verify_signature(WEBHOOK_SECRET, headers.get("X-Etrn-Signature", ""), body):
return401
event = json.loads(body)
if is_processed(event["id"]): # at-least-once: доставка может повторитьсяreturn200
waybill = event["data"]["waybill"]
if event["type"] == "waybill.status_changed":
... # обновите рейс: waybill["external_ref"] → waybill["status"]
mark_processed(event["id"])
return200# любой 2xx = доставлено, иначе ретраи до 24 ч
const replayWindow = 5 * time.Minute // защита от replay// verifyEtrnSignature проверяет заголовок// X-Etrn-Signature: t=<unix>,v1=<hex(hmac_sha256(secret, t + "." + body))>.func verifyEtrnSignature(secret, header string, body []byte) bool {
var ts, v1 stringfor _, part := range strings.Split(header, ",") {
if after, ok := strings.CutPrefix(part, "t="); ok {
ts = after
}
if after, ok := strings.CutPrefix(part, "v1="); ok {
v1 = after
}
}
t, err := strconv.ParseInt(ts, 10, 64)
if err != nil {
returnfalse
}
if d := time.Since(time.Unix(t, 0)); d > replayWindow || d < -replayWindow {
returnfalse// метка времени вне окна
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(ts))
mac.Write([]byte("."))
mac.Write(body)
got, err := hex.DecodeString(v1)
if err != nil {
returnfalse
}
return hmac.Equal(mac.Sum(nil), got) // сравнение за постоянное время
}
Экспедитору: поручения, расписки и заявки в кабинете #
Без API и без 1С: как экспедитор ведёт цепочку «поручение клиента → расписка → заявка перевозчику → накладная»
в веб-кабинете cabinet.etrn.app. Названия разделов и кнопок — как на экране.
🧰
Что нужно заранее
Кабинет организации; подключённый оператор ЭДО с электронными перевозочными документами (раздел «Операторы ЭДО» — сейчас Астрал или СберКорус);
электронная подпись на токене с КриптоПро-плагином в браузере — та же, которой подписывают «Новую ЭТрН»; если подписывает сотрудник, а не руководитель — МЧД.
1. Включите признак экспедитора
«Реквизиты» → блок «Организация — экспедитор»: поставьте галочку, укажите номер в реестре ГосЛог (он начинается с GL-) и дату включения, «Сохранить».
Без признака клиенты не смогут адресовать вам поручения, а расписка упрётся в «нужен признак экспедитора» — это требование формата ФНС, оператор ЭДО его проверяет.
Признак есть и у компании, и у ИП.
2. Примите поручение клиента
Раздел «Заявки» → вкладка «Ваш ход»: входящее поручение (ЭПЭ) лежит здесь. В карточке — стороны, договор, адреса приёма и выдачи, период отгрузки, груз.
«Принять поручение» → подпись → документ уходит клиенту, стадия «Готово». Отказаться от поручения форматом не предусмотрено: если условия не подходят,
клиент отзывает поручение сам (у него в карточке — «Отозвать поручение»).
3. Выпишите расписку
В карточке принятого поручения две кнопки. «Выписать расписку» — экспедиторская (ЭЭР): груз принят во владение для перевозки.
«Складская расписка» — ЭСР: груз принят на хранение. Форма предзаполнена из поручения: стороны, договор, груз, УИД поручения.
Допишите дату приёма, состояние груза и обязательства; у складской — ещё адрес склада, чей он (ваш или другого владельца), срок и время приёма,
состояние тары, упаковки, маркировки и пломб, и кто принял груз, если это сотрудник — вместе с документом о его полномочиях.
«Проверить и подписать» → подпись → расписка у клиента.
4. Закажите перевозку и оформите накладную
Там же, в карточке поручения: «Заявка перевозчику» — форма заказ-заявки (ЭЗЗ) с грузом и адресами из поручения; укажите перевозчика по ИНН, время подачи, требования к машине и цену.
Перевозчик отвечает титулом Т2 — водитель, машина, стоимость; стадия заявки станет «Готово». В карточке согласованной заявки появится «Выписать ЭТрН»:
«Новая ЭТрН» откроется с заполненными сторонами, водителем и машиной, вам — проверить и подписать. «Передать экспедитору» — субэкспедиция: поручение следующему экспедитору по тому же грузу.
5. Исправьте выданную расписку
Ошиблись в расписке, которая уже ушла клиенту? Откройте её карточку: когда расписка доставлена и получила УИД в ГИС ЭПД, там есть «Исправить расписку».
По правилам формата ФНС доставленную расписку не правят, а заменяют: платформа создаёт черновик новой расписки с той же формой и запоминает, вместо какой она выписана,
вы правите реквизиты и подписываете. Клиент получит новую расписку; в цепочке она помечена «взамен», прежняя остаётся в истории.
Заменить можно только последнюю версию — у уже заменённой расписки кнопки нет, она появляется в карточке новой.
Если расписку отклонила ГИС ЭПД уже после присвоения УИД, та же кнопка заведёт исправление с датой исправления — так предписывает формат.
6. Следите за ходом и документами
Вкладки «Ваш ход» · «Ждём ответа» · «Готово» · «Отклонено» · «Отозвано» — чей сейчас ход; фильтры «Входящие / Исходящие» и по виду («Заявки», «Поручения», «Расписки», «Складские»).
В карточке любого документа — «Цепочка рейса»: поручение, расписки, заявки и накладные одним списком, из каждого звена открывается следующее.
«Печатная форма» даёт лист для печати или PDF; рядом с каждым титулом — XML и файл подписи, это и есть юридически значимый документ.
О каждом вашем ходе приходит письмо на почту организации. Списание — за каждый подписанный титул, включая ответные и исправления.
Заказ-заявка (ЭЗЗ) приходит от заказчика — грузоотправителя или экспедитора. Ответ на неё — титул Т2 с водителем, машиной и ценой;
после этого заказчик выписывает накладную, а водитель подписывает свои титулы в приложении «Куб Транспорт».
🧰
Что нужно заранее
Кабинет перевозчика (у ИП — разделы «Мои рейсы» и «Заявки», у компании — «Заявки»); подключённый оператор ЭДО в «Операторы ЭДО»; подпись на токене с КриптоПро-плагином.
Заявку можно получить только от заказчика, который видит вас у оператора ЭДО, — проверьте, что связка в «Операторах ЭДО» зелёная.
1. Входящая заявка
«Заявки» → вкладка «Ваш ход». В карточке: кто заказывает (грузоотправитель или экспедитор), откуда и куда везти, когда подать машину, груз с числом мест и массой,
требования к транспорту, условия перевозки. Заказчик-ИП показан по ФИО — так устроен формат.
2. Примите или отклоните
«Принять заявку»: диспетчер с телефоном, водитель (ФИО, телефон, серия и номер водительского удостоверения с датой выдачи, ИНН), машина (госномер, тип, марка,
грузоподъёмность, вместимость), при необходимости прицеп, стоимость без НДС и с НДС или порядок расчётов. Что не хватает, форма подсветит до подписи.
«Отклонить» — тот же титул с причиной отказа; заказчик увидит её в карточке. Подпись — токеном в браузере.
Ответ в приложении «Куб Транспорт» для ИП готовится; до его выхода отвечайте из кабинета.
3. Накладная по заявке
Когда заявка согласована, заказчик выписывает по ней ЭТрН: стороны, водитель и машина берутся из вашего ответа. Накладная появится в «Мои рейсы» (у ИП) или «Накладные»,
водитель подпишет приём груза (Т2) и выдачу (Т4) в приложении. Заявка и накладная связаны: из карточки одной открывается другая.
4. Если что-то пошло не так
«Оператор не принял» в карточке — причина оператора рядом, чаще всего это данные формы: исправьте и подпишите заново, отклонённый титул не списывается.
Заявку, на которую вы уже ответили, заказчик переделать не может — он создаёт новую. Вопросы — в чат поддержки из кабинета, с номером документа.
Заказчику: поручение экспедитору и заявка перевозчику #
Грузовладелец либо поручает груз экспедитору, либо заказывает перевозку у перевозчика напрямую. Оба документа создаются в разделе «Заявки»
кабинета грузоотправителя и подписываются той же подписью, что «Новая ЭТрН».
1. Поручение экспедитору
«Заявки» → «Поручение экспедитору». Форма по формату ФНС: вы (клиент) и экспедитор, договор транспортной экспедиции, грузоотправитель и грузополучатель,
адреса приёма и выдачи груза со страной, период отгрузки, вид транспорта (тип кузова — только для автомобильного и железнодорожного), груз с маркировкой,
массой нетто и брутто и страной происхождения. «Проверить и подписать» → подпись → стадия «Ждём ответа».
Когда экспедитор примет поручение — «Готово»; его расписки (экспедиторская или складская) придут входящими документами в тот же раздел.
2. Отозвать поручение
Отказа от поручения у экспедитора нет, зато у вас есть отзыв: в карточке принятого поручения — «Отозвать поручение», укажите дату, причину и сумму
фактических расходов экспедитора к возмещению, подпишите (титул Т3). Поручение станет «Отозвано».
3. Заявка перевозчику напрямую
«Заявки» → «Новая заявка»: перевозчик по ИНН (ИП — по ФИО), грузополучатель, адреса погрузки и выгрузки, время подачи машины, груз (места, масса брутто, объём,
размеры места, вид тары, способ определения массы), требования к машине. «Подписать и отправить» → подпись → «Ждём ответа».
Ответ перевозчика с водителем, машиной и ценой появится в карточке; стадия «Готово». Дальше — «Выписать ЭТрН»: черновик накладной уже заполнен из заявки и ответа.
4. Что приходит от экспедитора
Расписки — подтверждение, что груз принят: экспедиторская (во владение) или складская (на хранение, со складом и условиями). Действий по ним не нужно, они лежат входящими в списке
и в «Цепочке рейса» вашего поручения. Исправленная расписка приходит новой версией с пометкой «исправление» — действует последняя.
Если экспедитор везёт сам, накладную он выпишет от своего имени с признаком экспедитора, а вы будете указаны клиентом в разделе 1а.
Большинство наших интеграций живут в 1С (УАТ, ERP, УТ, самописные конфигурации).
Ничего специального не требуется: платформа умеет https и JSON из коробки, схема
та же — POST /v1/waybills на подачу, вебхук или поллинг на статусы.
Ниже — рабочие куски на встроенном языке и грабли, на которых спотыкаются почти все.
🩸
Первая и самая частая ошибка — BOM
ЗаписьJSON отдаёт строку, а привычное
ПолучитьДвоичныеДанныеИзСтроки(Тело, КодировкаТекста.UTF8) дописывает в начало
метку порядка байтов. Для JSON это мусор перед {, и запрос падает с
bad-request ещё до разбора полей. Третий параметр
ДобавлятьBOM = Ложь — обязателен.
Общий модуль: запрос к API
// Один вход для всех вызовов: заголовки, тело без BOM, разбор ответа.Функция ВыполнитьЗапрос(Метод, Ресурс, Данные = Неопределено, КлючИдемпотентности = "") Экспорт
Заголовки = Новый Соответствие;
Заголовки.Вставить("Authorization", "Bearer " + КлючAPI());
Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
// Повтор с тем же ключом 24 часа безопасен: вернётся тот же ответ,// а не вторая накладная. Берите GUID документа — он у вас уже есть.Если ЗначениеЗаполнено(КлючИдемпотентности) Тогда
Заголовки.Вставить("Idempotency-Key", КлючИдемпотентности);
КонецЕсли;
Соединение = Новый HTTPСоединение("api.etrn.app", 443, , , ,
60, Новый ЗащищенноеСоединениеOpenSSL());
Запрос = Новый HTTPЗапрос(Ресурс, Заголовки);
Если Данные <> НеопределеноТогда
Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку(Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет));
ЗаписатьJSON(Запись, Данные);
ТелоСтрокой = Запись.Закрыть();
// ↓ Ложь — это «не добавлять BOM». Без неё сервер вернёт 400.
Запрос.УстановитьТелоИзДвоичныхДанных(
ПолучитьДвоичныеДанныеИзСтроки(ТелоСтрокой, КодировкаТекста.UTF8, Ложь));
КонецЕсли;
Ответ = Соединение.ВызватьHTTPМетод(Метод, Запрос);
Результат = Новый Структура("Код, Тело", Ответ.КодСостояния, Неопределено);
ТелоОтвета = Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
Если ЗначениеЗаполнено(ТелоОтвета) Тогда
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(ТелоОтвета);
Результат.Тело = ПрочитатьJSON(Чтение, Истина); // Истина = в Соответствие
Чтение.Закрыть();
КонецЕсли;
Возврат Результат;
КонецФункции
Ошибки приходят в формате application/problem+json. Разбирать их удобно
один раз в том же модуле: у любой ошибки есть title (что случилось),
detail (что делать) и type — ссылка на страницу с описанием.
У 422 дополнительно приходит errors с именами полей титула.
Подача накладной и разбор ошибок
Процедура ПодатьНакладную(ДокументСсылка) Экспорт
Титул = СобратьТитул(ДокументСсылка); // ваша структура по схеме Waybill
Ответ = ВыполнитьЗапрос("POST", "/v1/waybills", Титул,
Строка(ДокументСсылка.УникальныйИдентификатор()));
Если Ответ.Код = 201 Тогда
ЗаписатьРезультат(ДокументСсылка, Ответ.Тело["id"], Ответ.Тело["status"]);
Возврат;
КонецЕсли;
Если Ответ.Код = 422 Тогда// Титул не прошёл проверку: конкретные поля — в errors.Для Каждого Ошибка Из Ответ.Тело["errors"] Цикл
Сообщить(Ошибка["field"] + ": " + Ошибка["message"]);
КонецЦикла;
ИначеЕсли Ответ.Код = 429 Тогда// Слишком часто. Сколько ждать — в заголовке Retry-After.
ОтложитьПовтор(ДокументСсылка);
ИначеЕсли Ответ.Код = 403 Тогда// Юрлицо-отправитель не принадлежит ключу: опечатка в ИНН или юрлицо не в группе.
Сообщить(Ответ.Тело["detail"]);
Иначе
Сообщить(Ответ.Тело["title"] + ". " + Ответ.Тело["detail"]);
КонецЕсли;
КонецПроцедуры
Приём вебхуков в HTTP-сервисе 1С
Публикуете HTTP-сервис на веб-сервере, его адрес указываете при создании подписки.
Два правила, которые важнее остального: отвечайте сразу — записали
событие в регистр и вернули 200, а разбирать будете регламентным заданием
(мы ждём ответ 10 секунд, дольше — считается неудачей и уходит в повтор);
и будьте идемпотентны по event.id — одно событие
может прийти дважды, это нормальная работа доставки, а не сбой.
Модуль HTTP-сервиса: приём и проверка подписи
Функция СобытиеPOST(Запрос)
ТелоДД = Запрос.ПолучитьТелоКакДвоичныеДанные();
Подпись = Запрос.Заголовки.Получить("X-Etrn-Signature");
ЕслиНе ПодписьВерна(ТелоДД, Подпись) ТогдаВозвратНовый HTTPСервисОтвет(401);
КонецЕсли;
Тело = ПолучитьСтрокуИзДвоичныхДанных(ТелоДД, КодировкаТекста.UTF8);
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(Тело);
Событие = ПрочитатьJSON(Чтение, Истина);
Чтение.Закрыть();
// Тип ping приходит только по вашему запросу — это проверка связи.// Незнакомые типы просто пропускаем: их список со временем растёт.Если Событие["type"] <> "ping"Тогда// Только записать. Разбор — регламентным заданием: ответ должен// уйти за секунды, иначе доставка считается неудачной.
РегистрыСведений.ЕТрН_ВходящиеСобытия.Записать(Событие); // по event.id, повтор не дублируетКонецЕсли;
ВозвратНовый HTTPСервисОтвет(200);
КонецФункции// Подпись: t=<unix>,v1=<hex(hmac_sha256(secret, t + "." + тело))>Функция ПодписьВерна(ТелоДД, ЗаголовокПодписи)
ЕслиНе ЗначениеЗаполнено(ЗаголовокПодписи) ТогдаВозвратЛожь;
КонецЕсли;
Метка = ""; ПодписьHex = "";
Для Каждого Часть Из СтрРазделить(ЗаголовокПодписи, ",") ЦиклЕсли СтрНачинаетсяС(Часть, "t=") Тогда
Метка = Сред(Часть, 3);
ИначеЕсли СтрНачинаетсяС(Часть, "v1=") Тогда
ПодписьHex = НРег(Сред(Часть, 4));
КонецЕсли;
КонецЦикла;
// Защита от повтора чужой записи: метка старше 5 минут не принимается.ЕслиНе ЗначениеЗаполнено(Метка)
ИлиМакс(0, ТекущаяУниверсальнаяДатаВМиллисекундах() / 1000
- Число(Метка)) > 300 ТогдаВозвратЛожь;
КонецЕсли;
// Подписывается строка "<метка>." и СЫРОЕ тело — склеиваем в буфере,// чтобы не потерять байты на перекодировках.
Префикс = ПолучитьДвоичныеДанныеИзСтроки(Метка + ".", КодировкаТекста.UTF8, Ложь);
Данные = ОбъединитьДвоичныеДанные(Префикс, ТелоДД);
Ключ = ПолучитьДвоичныеДанныеИзСтроки(СекретПодписки(), КодировкаТекста.UTF8, Ложь);
// HMAC() — платформа 8.3.21 и старше. На младших — реализация через// ХешированиеДанных и БуферДвоичныхДанных (ipad/opad), см. документацию.
Наша = НРег(ПолучитьHexСтрокуИзДвоичныхДанных(HMAC(Ключ, Данные, ХешФункция.SHA256)));
Возврат Наша = ПодписьHex;
КонецФункции
📡
Проверьте сервис, не дожидаясь рейса
Опубликовали HTTP-сервис — вызовите POST /v1/webhooks/{id}/ping: придёт тестовое
событие с настоящей подписью, а в ответе вы увидите, что ответил ваш веб-сервер. Самый быстрый
способ поймать типовые «не туда опубликовали», «не пускает аутентификация IIS/Apache» и
«сертификат не той цепочки» — до того, как поедет первая накладная.
Чек-лист перед боем
BOM выключен — третий параметр Ложь в
ПолучитьДвоичныеДанныеИзСтроки.
Ключ хранится в безопасном месте — не в общем модуле и не в реквизите
справочника, который открывают все. Ключ = право подписывать от лица организации.
Идемпотентность на подаче — Idempotency-Key с GUID документа:
обрыв связи после отправки не создаст вторую накладную.
Таймаут соединения — 60 секунд, не по умолчанию: подача с большим титулом
и медленным каналом иначе рвётся на ровном месте.
Веб-сервис отвечает быстро — запись в регистр и 200; вся логика — в
регламентном задании.
Песочница сначала — тестовый ключ etrn_test_… и
simulate прогонят накладную по всем статусам за минуты, включая вебхуки.
Если вы возите под несколькими юрлицами, есть два пути. Оба рабочие — выбор
зависит от того, как устроена ваша система.
Путь
Когда подходит
Ключ на каждое юрлицо
Ваша система и так знает, от какого юрлица выписывает накладную, — она просто берёт нужный ключ. Ничего настраивать не нужно, работает сегодня.
Один ключ на группу
Одна конфигурация на холдинг, один список накладных по всем юрлицам, одна подписка на вебхуки. Юрлицо-отправитель указываете в самом запросе.
⚠️
Что группа НЕ меняет
Число подключений к операторам ЭДО. Ящик оператор выдаёт конкретному юрлицу — это его абонент,
его сертификат, его договор. Шесть юрлиц у четырёх операторов — это 24 подключения и с одним
ключом, и с шестью. Группа экономит настройки интеграции, а не подключения.
В кабинете, раздел «Группа компаний», создайте группу и пригласите юрлица по ИНН.
Каждое приглашённое юрлицо подтверждает участие в своём кабинете. До подтверждения
ключ от его имени не работает — иначе любой, зная чужой ИНН, подавал бы документы за чужую компанию.
В разделе «API» выпустите ключ на группу. Старые ключи продолжают работать как прежде.
Что меняется в запросах
Подача не меняется. ИНН грузоотправителя вы и так передаёте в parties
с ролью shipper — платформа сама выберет ящик ЭДО этого юрлица.
Список отдаёт накладные всех юрлиц группы. ?org_inn= сужает до одного.
В каждой накладной и в каждом событии есть org_inn — чьё это юрлицо.
Одна подписка на вебхуки принимает события всех юрлиц.
Лимит запросов умножается на число юрлиц группы: ключ работает за несколько компаний.
🚫
Юрлицо вне группы — 403
Подача или чтение от имени ИНН, которого нет в группе (или он не подтвердил участие), отвечает
403 forbidden-org со списком доступных ИНН. Это не ошибка ключа — это состав группы:
поправьте его в кабинете.
Подключение операторов за юрлица группы
Раздел кабинета «Операторы ЭДО» показывает все юрлица группы строками:
разверните нужное — под ним откроются его операторы, его подключения и его кнопка
«Подключить всех». Ходить по кабинетам каждого юрлица не нужно.
Право появляется в тот момент, когда юрлицо подтверждает участие в группе:
подтверждение и есть его согласие, а выход из группы забирает право тем же действием.
Каждое действие за другое юрлицо записывается, и его представителю уходит письмо. Сам участник
видит список этих действий у себя в кабинете, в разделе «Группа компаний».
🔑
Часть операторов подключает только само юрлицо
Заявку по реквизитам (СберКорус) можно подать за участника группы. А вот Астралу нужен файл
сертификата подписи этой организации, вход в СБИС идёт её ключом или паролем, а Контур — это
вход в её существующий кабинет Контур.ID. Ключ лежит у юрлица, и предъявить его может только
тот, у кого он есть: в таких карточках кабинет предлагает не «подключить», а
позвать — участнику уходит письмо с просьбой сделать это у себя.
Проверить результат из интеграции можно там же, где мастер-данные:
GET /v1/organization отдаёт по каждому юрлицу группы список операторов и статус
подключения — не заходя в кабинет.
Чтобы подать накладную, достаточно ИНН участников — платформа сама найдёт их ящики.
Но если вы ведёте справочник контрагентов у себя, полезно один раз забрать
идентификаторы ЭДО: по ним видно, у какого оператора заведён перевозчик и достижим ли
он вообще, ещё до планирования рейса.
Отдаёт юрлицо ключа, тариф с остатком лимита и список операторов ЭДО. У каждого оператора —
статус подключения и participant_id: идентификатор, под которым вы заведены
у этого оператора (BoxId у Диадока, ИдАбонента у Астрала, числовой у СБИС).
У оператора со статусом pending или error идентификатора нет —
он появляется только вместе с подключением.
Если ключ выпущен на группу компаний, в ответе дополнительно приходят
group и orgs[] — все подтвердившие юрлица, у каждого свой список
операторов. Ключ, выпущенный на одно юрлицо, вернёт только его: это не ошибка, а
область действия ключа — перевыпустите его в кабинете с областью «Вся группа».
Шаг 2. Собрать справочник перевозчиков — GET /v1/counterparties/{ИНН}
Делается один раз на контрагента и кладётся в ваши мастер-данные. Ответ говорит три вещи:
достижим ли он в ЭДО вообще (reachable), у какого оператора
заведён (operators[], флаг roaming — «у чужого оператора, дойдёт роумингом»)
и под каким идентификатором — participant_id.
Здесь participant_id — это ИдУчастникаЭДО из реестра ФНС
вида 2BM-ИНН-КПП-серийник. Первые три знака сами называют оператора, так что
спрашивать отдельно не нужно:
Свой собственный ИдУчастникаЭДО берётся этой же ручкой — по своему ИНН, ограничения
«только чужие» нет.
Шаг 3. Подача — и надо ли вообще подставлять идентификатор
Не надо. В parties[] обязательны только role и
inn — по ним платформа сама находит ящик получателя: сначала у своего оператора
по ИНН и КПП, а если контрагент у чужого — заводит роуминговый ящик-шлюз. Приглашений и
ручных заявок для этого не нужно.
Поле operator_subscriber_id существует для случая «мы и так знаем ид абонента»:
оно экономит один-два запроса к оператору на каждой подаче.
⚠️
participant_id и operator_subscriber_id — разные вещи
У Астрала, СБИС и СберКоруса ид абонента и есть ИдУчастникаЭДО (2AE,
2BE, 2BK) — там подстановка сработает. А Контур.Диадок адресует
ящик своим идентификатором (GUID вида
ddb51104-ea2a-…), и ИдУчастникаЭДО 2BM-… для него не BoxId.
Платформа это распознаёт: если в operator_subscriber_id пришёл ид участника
ФНС, он трактуется как «ищи по реквизитам», и накладная всё равно доезжает. Но полагаться
на это не стоит — либо кладите ид абонента того же оператора, либо не кладите
ничего и оставьте поиск платформе.
Шаг 4. Дальше — вебхуки
Статусы, входящие титулы и подписи приходят на ваш URL сами — опрашивать список не нужно.
См. Вебхуки.
Всё ниже — из настоящих интеграций последних недель. Если вашего вопроса нет, напишите нам: ответ появится здесь.
Получили 504 на подаче. Накладная создана или нет?
Не знаете — и не должны. Через пару минут повторите тот же запрос с тем же титулом: завершённая подача вернёт 201 с той же накладной, не принятая оператором — уйдёт заново, а пока исход неизвестен, повтор получит 409 с reason: submission_pending. Дубля не будет ни в одном случае. См. таймауты.
Получили 424 operator-reauth-required. Что делать?
Ничего в коде. Оператор перестал принимать доступ клиента (обычно после смены пароля или входа с другого устройства). Клиенту нужно войти к оператору заново из кабинета «Куб Транспорт»: Операторы ЭДО → оператор → Подключить. Платформа отправит ему письмо с этой инструкцией сама. Повторяйте подачу после входа.
Почему 403 forbidden-org, если ключ рабочий?
Ключ принадлежит организации, а в parties[role=shipper] стоит ИНН другого юрлица. Либо это опечатка, либо юрлицо нужно добавить в группу компаний и выпустить групповой ключ. В ответе перечислены ИНН, доступные ключу.
Сколько ждать Т2 от водителя и как о нём узнать?
Столько, сколько водитель едет до погрузки: Т2 он подписывает в приложении при приёме груза. Подпишитесь на waybill.document_added и waybill.status_changed — или читайте GET /waybills?updated_from= раз в несколько минут.
В ответе на создание gis_uid пустой. Это ошибка?
Нет. УИД ГИС ЭПД присваивает государство после регистрации титула, обычно в течение минут. Он придёт событием waybill.gis_uid_assigned и появится в карточке. Подавайте следующие накладные, не дожидаясь.
Подпись у нас валидная, а платформа отвечает «контейнер не разобран».
Чаще всего в поле лежит не отсоединённая CMS: PEM с заголовками -----BEGIN, сертификат вместо подписи или присоединённая подпись с документом внутри. BER и двойной base64 мы принимаем сами. Прогоните файл через POST /waybills/validate: ответ покажет поле и причину.
У Астрала получатели адресуются абонентскими идентификаторами 2AE…. Платформа берёт их из ИдФайл титула; если титул сформирован без идентификаторов участников, передайте их явно в parties[].operator_subscriber_id.
Подача отвечает 409 с reason: submission_pending. Что делать?
Запрос с этим титулом уже ушёл оператору ЭДО, а ответа нет: он не пришёл, оборвался или не разбирается, пришла ошибка 500/502/504 или оператор не уложился в срок ответа. Так же отвечает повтор, пока первая подача ещё в пути. Исход неизвестен — документ мог появиться у оператора, поэтому платформа держит бронь на титул и не отправит его повторно. Не повторяйте подачу в цикле и не пересоздавайте титул с новым ИдФайл — так у оператора может появиться второй документ. Подачи в Контур платформа сверяет сама: раз в 10 минут ищет титул по ИдФайл и байтам в ящике, через который подача ушла; если просмотреть ящик полностью не удаётся, блок остаётся. Нашла — накладная появляется, приходит вебхук waybill.created, а повтор этого титула (в том числе подписанного заново) возвращает её. Убедилась, что документа у оператора нет, — снимает блок, и повтор уходит оператору заново, но не раньше чем через час после неудачной подачи и только когда ящик просмотрен полностью и синхронизирован уже после этого часа. Обычно всё решается в пределах часа после восстановления связи с оператором; для других операторов срок не обещаем. Если через пару часов ничего не изменилось, пришлите request_id в поддержку. После 503 operator-throttled и 424 всё иначе: там титул к оператору не ушёл, и повтор того же титула уходит заново.
Оператор отклонил титул, причину мы устранили. Почему повтор отвечает 409?
Платформа не отправляет оператору повторно ту же подписанную версию, которую он уже отклонил: 409 conflict «эта версия уже отклонена оператором». Подпишите титул заново и подайте с новым Idempotency-Key. Новая подпись — новая отправка, второго документа у оператора не будет, потому что первый он не принял.
«Контур.Диадок не принял документ: у плательщика закончился оплаченный пакет документов».
Подачу у Контура оплачивает сторона титула из своего пакета документов: Т1 — грузоотправитель, Т2 — перевозчик. В detail указан ИНН плательщика. Пополните баланс у Контура и подайте титул новой подписью.
Контур: «Подписание доступно только участнику погрузки».
Титул подан от ящика, которого нет среди участников накладной. Проверьте, что ИНН и идентификатор отправителя в титуле — это ящик клиента, авторизованный в кабинете, а не другое юрлицо группы.
У перевозчика нет ЭДО. Накладная дойдёт?
Нет: Т2 подписать некому. GET /counterparties/{inn} покажет это до подачи. Перевозчика можно пригласить в платформу из кабинета — подключение и выпуск подписи он проходит сам, в приложении.
Одна подписка на вебхуки получает и песочницу, и бой. Как отличать?
По полю operator в событии: у песочницы "sandbox", у боя код реального оператора. Либо заведите отдельный URL на время отладки и удалите подписку после.
Как связать накладную с рейсом в нашей ТМС?
Передайте свой номер в external_ref при подаче: он возвращается в карточке, в списке (фильтр ?external_ref=) и в каждом событии. Идентификатор на нашей стороне хранить не обязательно.
Можно ли отозвать или исправить поданную накладную через API?
В v1 нет: аннулирование и исправления — это отдельные титулы, которые стороны подписывают у оператора, и они приходят к вам в цепочке документов. Исправленный титул Т1 подаётся как новая накладная с новым ИдФайл.
Мы холдинг из шести юрлиц. Шесть ключей или один?
Работают оба варианта. Один групповой ключ даёт единый список, единую подписку на события и лимит, умноженный на число юрлиц. Подключений к операторам это не уменьшает: ящик выдаётся каждому юрлицу. См. Несколько юрлиц.
Что вы делаете, когда у вас что-то ломается?
Любая серверная ошибка в этом API поднимает дежурного немедленно, отказы операторов классифицируются и не превращаются в 500. В ответе всегда есть request_id — с ним мы находим запрос вместе с ответом оператора за минуты.
Полный контракт с телами запросов и схемами ответов — в OpenAPI-спеке.
📍
Пути указаны относительно базового URL
Полный адрес — https://api.etrn.app/v1 + путь из таблицы. Например
GET /counterparties/{inn} вызывается как
https://api.etrn.app/v1/counterparties/7731162754. Забыли префикс —
ответ 404 подскажет правильный адрес.
Ручка
Назначение
Скоуп
POST/waybills
подать ЭТрН (титул Т1)
waybills:write
POST/waybills/validate
проверить титул без подачи — ничего не списывается
Все ошибки — в формате RFC 9457 Problem Details (application/problem+json):
type — стабильный URI-классификатор для машинной обработки, title и
detail — человеку, request_id — приложите к обращению в поддержку,
errors[] — пополевые детали при 422. HTTP-статус в теле не дублируется: источник
один — статус ответа. Серверных 500 в этом API быть не должно: любая такая ошибка поднимает дежурного.
424 Failed Dependency
{
"type": "https://api.etrn.app/errors/operator-reauth-required",
"title": "Оператор ЭДО ждёт повторного входа клиента",
"detail": "Контур отверг доступ организации ИНН 5038129757: клиенту нужно заново войти в Контур из кабинета (Операторы ЭДО → Контур → Подключить). Повторяйте подачу после входа.",
"request_id": "req_c81e728d"
}
Внутри одного type машинную причину может уточнять поле reason — расширение
RFC 9457. У накладных оно есть у 409 conflict при подаче; у заявок и экспедиции — у каждого
своего классификатора и повторяет машинный код причины платформы. Незнакомые значения
трактуйте как обычный conflict.
409 Conflict — исход подачи неизвестен
{
"type": "https://api.etrn.app/errors/conflict",
"title": "По этому титулу уже есть подача, её исход сверяется",
"detail": "Отправка ушла оператору ЭДО, но ответ от него не пришёл. Повтор заблокирован, чтобы у оператора не появился дубль. Если за час ничего не изменится, напишите в поддержку — проверим у оператора",
"request_id": "req_3c59dc04",
"reason": "submission_pending"
}
Классификатор
HTTP
Когда
Повторять?
Что делать
bad-request
400
тело не JSON, лишний параметр, неверный формат поля
нет
исправьте запрос по detail
unauthorized
401
ключ не передан, неверен или отозван
нет
проверьте заголовок Authorization и статус ключа в кабинете
insufficient-scope
403
у ключа нет нужного скоупа
нет
выпустите ключ со скоупом waybills:write / webhooks:manage (заявки и экспедиция — orders:write / forwarding:write)
forbidden-org
403
ИНН отправителя не принадлежит ключу
нет
сверьте parties[role=shipper].inn со списком юрлиц ключа; поправьте состав группы в кабинете
sandbox-only
403
ручка песочницы вызвана боевым ключом
нет
simulate есть только у etrn_test_-ключей
not-found
404
нет такой накладной, документа или подписки — либо она чужая
нет
проверьте id и организацию ключа; забытый префикс /v1 тоже даёт 404 с подсказкой
idempotency-conflict
409
тот же Idempotency-Key с другим телом
нет
новый запрос — новый ключ
conflict
409
действие невозможно в текущем состоянии (например, simulate назад по циклу). При подаче причину называет поле reason: submission_pending — по этому титулу уже есть подача, запрос ушёл оператору, а ответа нет (исход неизвестен); submission_rejected — эта версия уже отклонена оператором
нет
submission_pending — не пересоздавайте титул: подачи в Контур платформа сверяет сама, через пару часов без изменений — request_id в поддержку; submission_rejected — новая подпись; без reason — прочитайте карточку и повторите с учётом состояния
validation-failed
422
титул, подпись или стороны не прошли проверку — либо оператор ЭДО отклонил документ по существу; у заявок и экспедиции — поля формы в snake_case, при создании черновик сохранён и его id — в document_id (title «Оператор ЭДО отклонил документ», причина и действие в detail)
после исправления
детали в errors[]; отладка — POST /waybills/validate; см. типичные причины
external-ref-taken
409
документ с таким external_ref уже есть у этого юрлица (заявки и экспедиция)
нет
работайте с существующим — его id в existing_id
title-changed
409
форма менялась после сборки титула: подпись поставлена на старые байты
после пересборки
заберите титул заново (…/send, …/accept) и подпишите новые байты
awaiting-operator
409
документ уже ушёл оператору ЭДО, исход ещё не ясен
нет
следите за status в карточке; повтор подачи не нужен
action-not-allowed
409
действие не подходит к статусу или стороне документа: нет собранного титула, на документ уже отвечено
нет
сверьтесь с available_actions[] карточки
order-number-taken
409
номер и дата документа уже поданы этим отправителем
нет
возьмите другой номер или найдите поданный документ в списке
signer-mismatch
422
сертификат подписи принадлежит не стороне титула
после исправления
подписывайте сертификатом юрлица-стороны либо по МЧД с signer.poa
sign-grant-required
403
у юрлица ключа нет подтверждённой связки ЭДО, дающей право подписи за эту сторону
отказа от поручения нет в формате ФНС — отзывайте; у поручения ещё нет УИД ГИС ЭПД; нет подключённого оператора для отправки; оператор не поддерживает этот вид документа; к оператору контрагента документ роумингом не ходит
по ситуации
detail говорит, что сделать; оператора можно сменить полем operator
operator-reauth-required
424
оператор отверг доступ клиента: нужен повторный вход
после входа клиента
покажите detail клиенту; письмо с инструкцией он получит от платформы
operator-access-revoked
424
у платформы отозваны права на организацию у оператора
после выдачи прав
клиент выдаёт права в кабинете оператора (текст в detail)
operator-api-not-enabled
424
оператор не включил клиенту обмен по API
после включения
клиент обращается в поддержку оператора; повтор после
rate-limited
429
превышен лимит запросов ключа
через Retry-After
следите за X-RateLimit-Remaining
upstream-unavailable
502
часть платформы не ответила
да, с паузой
повторите через несколько секунд; если держится — request_id в поддержку
operator-throttled
503
оператор ЭДО недоступен или перегружен. На подаче: документа у оператора нет — запрос не дошёл или оператор явно отказал; бывает и на чтении документов цепочки и QR
через Retry-After (сейчас 120 с)
повторите тот же титул — он уйдёт оператору заново
operator-disabled
503
оператор отключён платформой (обслуживание, авария); причина — в detail
нет, Retry-After не приходит
не передавайте operator или выберите другой из GET /operators
counterparty-check-unavailable
503
справочник оператора для проверки контрагента недоступен
да, позже
подавайте без проверки: достижимость проверится при подаче
upstream-timeout
504
платформа не ответила за 30 секунд (редко: на подачу она отвечает не позже 25); подача могла начаться, начатая продолжается в фоне
да, тем же документом через пару минут
завершённая подача вернёт ту же накладную, не принятая — уйдёт заново, пока исход неизвестен — 409 submission_pending; см. таймауты
internal
500
авария платформы
да, с паузой
дежурный уже уведомлён; приложите request_id
Отдельный случай при подаче: явно переданный неизвестный или выключенный код
operator отклоняется как 422 ещё на валидации запроса — до похода к оператору.
На ключ: 30 запросов можно послать сразу, дальше ёмкость пополняется со скоростью 10 в секунду. X-RateLimit-Limit — ёмкость (30), X-RateLimit-Remaining — сколько от неё осталось,
X-RateLimit-Reset — unix-время полного восстановления, X-RateLimit-Policy — политика целиком.
При превышении — 429 и Retry-After в секундах: столько ждать до следующего запроса
(это меньше, чем до полного восстановления).
Размер запроса подачи
титул до 2 МБ, всё тело запроса до 8 МБ
Ожидание оператора при подаче
ответ на подачу — не позже 25 секунд с начала её обработки, включая оформление лицензии оператора. Отправка к сроку не началась — 503 operator-throttled, титул не уходил; не ответил оператор — 409 conflict с reason: submission_pending, подача продолжается в фоне до 90 секунд, её итог получит повтор тем же титулом
Вебхуки
10 секунд на ответ вашего сервера; 8 попыток с паузами от 1 минуты до 12 часов; ping ждёт 8 секунд
Идемпотентность
Idempotency-Key до 128 символов, окно 24 ч. Повтор с тем же ключом — исходный результат (200), тот же ключ с другим телом — 409
Пагинация
limit до 200 (по умолчанию 50), курсор next_cursor; пустой курсор — страниц больше нет
Инкрементальное чтение
updated_from / updated_to — окно ПОСЛЕДНИХ ИЗМЕНЕНИЙ (RFC 3339). Накладная, которую с момента подачи не меняли, попадает в окно по времени создания, поэтому свежие подачи не теряются. Окно действует на каждой странице: next_cursor запоминает его сам, повторять при переходе не нужно. Повторить можно — значение из запроса главнее, так что окно допустимо сузить на ходу. Остальные фильтры курсор не запоминает
Время
Все created_at / updated_at — в UTC с суффиксом Z: 09:30:00Z — это 12:30 по Москве. Сравнивайте моменты, а не циферблат. Своё смещение в параметрах допустимо (…T12:00:00+03:00), но + в строке запроса кодируйте как %2B — иначе он станет пробелом и придёт 400
Цикл опроса
Курсор — не водяной знак, между циклами его не хранят. Каждый цикл: запрос без cursor с updated_from = наибольший updated_at прошлого цикла минус минута запаса, дальше по next_cursor, пока он не опустеет
Коротко о том, что менялось в API и платформе за ним. Всё внутри v1 аддитивно: старый код продолжает работать.
Дата
Что
14 октября 2026
Расписки по правилам обмена: менять их по своей инициативе нельзя, исправляют только по запросу ГИС ЭПД. POST /forwarding-receipts/{id}/correct исправляет расписку, отклонённую ГИС после присвоения УИД (блок correction, связь corrects); для доставленной или принятой отвечает 409 receipt-replacement-off. Входящие замены от контрагентов приходят как раньше, со связью replaces. См. раздел.
12 октября 2026, вечер
Складская расписка (ЭСР) в ресурсе /forwarding-receipts: kind: warehouse_receipt, форма со складом, владельцем и условиями хранения; POST /forwarding-orders/{id}/receipt принимает kind, список — фильтр ?kind=. Исправление расписок обоих видов — POST /forwarding-receipts/{id}/correct, блок correction в форме, связь corrects в GET /chain.
12 октября 2026
Заявки и экспедиторские документы в API: /orders (заявка перевозчику, ЭЗЗ), /forwarding-orders (поручение экспедитору, ЭПЭ), /forwarding-receipts (экспедиторская расписка, ЭЭР) и /chain. Титул собирает платформа из JSON-формы, вы подписываете байты XML и подаёте подпись отдельным вызовом; ответ контрагента — accept / decline / revoke; документы «на основании» поручения; связка заявки с накладной и форма ЭТрН по заявке. Новые классификаторы ошибок с reason, document_id у 422 при создании, existing_id у 409 external-ref-taken. Скоупы orders:* и forwarding:*; ключи с waybills:* работают без перевыпуска. См. раздел.
25 сентября 2026
503 operator-throttled приходит с заголовком Retry-After (120 секунд), а его title теперь «Оператор ЭДО недоступен»: этот классификатор означает не только перегрузку, но и недоступность оператора. У operator-disabledRetry-After нет — срок знает администратор. У 409 conflict при подаче появилось поле reason: submission_pending (исход прошлой подачи неизвестен, повтор заблокирован) или submission_rejected (версия уже отклонена оператором). Ответ на подачу — не позже 25 секунд с начала обработки, включая оформление лицензии: не начатая к сроку отправка не уходит вовсе (503 operator-throttled), а не ответивший к сроку оператор — 409 submission_pending, а не 504. 503 на подаче теперь всегда значит «документа у оператора нет»; всё неоднозначное, включая оборванный ответ, — 409 submission_pending. Чтение документов цепочки и QR при недоступном операторе — 503 operator-throttled вместо 404. Неизвестный исход подачи в Контур платформа сверяет сама: документ нашёлся — накладная появляется с вебхуком waybill.created, его нет — блок снимается, но не раньше чем через час. После 503 operator-throttled и 424 повтор того же титула уходит оператору заново, а завершённая подача отдаёт свою накладную без второй отправки и точному повтору запроса, и повтору, подписанному заново. Слаги и type не менялись.
22 сентября 2026
В GET /counterparties/{inn} добавлены participant_ids (все адреса контрагента без дублей) и поля оператора в operators: prefix, name, participant_id, integrated. Распознаётся весь действующий реестр операторов ФНС, а не четыре интегрированных: раньше идентификатор вроде 2AL-… (Такском) давал пустой operators. Уточнена формулировка reachable: это «найдена в реестре ЭДО», а не гарантия доставки.
8 сентября 2026
Курсор списка накладных запоминает окно изменений: продолжение выдачи по next_cursor остаётся тем же срезом, даже если updated_from не повторить. Раньше на второй странице окно терялось и возвращались давно не менявшиеся накладные. Ранее выданные курсоры остаются рабочими; остальные фильтры по-прежнему передаются на каждой странице.
3 сентября 2026
Повтор подачи после таймаута больше не создаёт второй документ у оператора: подача бронирует титул до отправки и продолжается в фоне; повтор дожидается результата. Письма клиенту о потере и восстановлении связи с оператором.
2 сентября 2026
Отказы операторов типизированы: 424 operator-reauth-required / operator-access-revoked / operator-api-not-enabled вместо 500. Ограничены бюджеты на получение документооборота и QR, каждая 5xx логируется с request_id.
30 августа 2026
Групповые ключи для холдингов: подача от любого юрлица группы, единый список с ?org_inn=, одна подписка на события. 403 forbidden-org со списком доступных ИНН. Титул разбирается при подаче: мусор отвечает 422, а не уходит оператору.
28 августа 2026
participant_id везде означает идентификатор участника ЭДО по ФНС; добавлен all_participant_ids. В GET /organization — список юрлиц группы с их операторами. Оплата Т1-API разовая: 402 из канала убран.
16 августа 2026
Запуск v1: подача Т1, карточка и цепочка документов Т1–Т9, QR, вебхуки с HMAC-подписью и ping, песочница по тестовому ключу с simulate, этот портал.
Пишите на support@etrn.app и прикладывайте
request_id из тела ошибки — по нему запрос находится целиком, вместе с ответом
оператора ЭДО. Если ошибки нет, а поведение странное, приложите время запроса с точностью до
минуты и префикс ключа (etrn_live_a1b2c3d4e…) — полное значение ключа присылать не
нужно никогда.