Перейти к содержимому
СервисАпп API
На этой странице

Запросы, версии и безопасные повторы

Формат запроса и ответа

GET читает данные; создание, изменение, архивирование, удаление и переходы выполняются POST. Поддерживаются только описанные GET/POST-пути. Не передавайте GET-тело, _method или заголовки переопределения метода.

Команда с телом JSON требует Content-Type: application/json, корректный JSON-объект и Idempotency-Key. Если у команды нет полей, передайте {}. Максимальный размер JSON — 2 МиБ. Неизвестные поля, недопустимые вложенные ключи и запись в поля только для чтения отклоняются. Повторяющиеся ключи JSON не принимаются.

Имена полей — snake_case. Даты: YYYY-MM-DD; моменты: ISO 8601 с Z или явным смещением; недельное время: HH:mm в context.timezone. Точные допустимые форматы и дробная часть времени приведены у параметров операции. updated_at возвращается без дробной части.

Деньги передаются как {"amount":"1250.00","currency":"RUB"}. amount — десятичная строка без экспоненты и разделителей тысяч; точность определяется валютой и ограничениями ресурса. Валюта совпадает с context.currency, конвертации нет. Количество и нормочасы — десятичные строки. Скидка строки акта — сумма на всю строку; скидка договора — процент.

Успешный JSON содержит data и meta, список также содержит внешние links пагинации. POST возвращает meta.operation_id, meta.affected_resources, meta.warnings; Location указывает на /api/v1/api-operations/{id}. Элемент affected_resources: {type,id,version,deleted}. Для удалённого объекта version=null. Предупреждение имеет code, message, resource_refs; пустой массив означает отсутствие предупреждений.

Каждый HTTP-ответ получает новый заголовок X-Request-Id. Поля request_id в JSON нет. Входной клиентский X-Request-Id не становится серверным ID. У 304 нет тела, скачивание файла не имеет JSON-обёртки. POST, повтор запроса, ошибки и скачивание используют Cache-Control: private, no-store.

Idempotency-Key

Формат ключа: 1–128 ASCII-символов A-Z, a-z, 0-9, ., _, :, -. Сохраните ключ вместе с URL, телом, файлами и предусловиями до отправки. Область уникальности — подключение внутри организации. Один ключ на другом пути того же подключения вызывает конфликт.

Повтор принятой команды с прежним ключом и тем же содержимым не выполняет действие заново. Проверяются метод, путь, тело запроса, версии и содержимое файлов. Порядок ключей JSON-объекта несущественен; порядок элементов массива, тип значения, ведущие нули, null и отсутствие ключа значимы. Для multipart/form-data учитываются kind, итоговое имя и содержимое файла, а также условия проверки версии. Разделитель частей запроса не влияет на сравнение.

Ситуация Действие клиента
Потерян ответ, превышение времени ожидания, 5xx Повторить исходный запрос с прежним ключом и предусловиями либо прочитать известную операцию
Получен исходный 200/201 и Idempotency-Replayed: true Применить сохранённый результат; свежую карточку получить отдельным GET
409 OPERATION_IN_PROGRESS Учесть Retry-After и опрашивать операцию; второго исполнителя не создавать
409 IDEMPOTENCY_CONFLICT Проверить, что ключ не использован для другого пути, тела, версии или файла
409 OPERATION_OUTCOME_UNKNOWN Проверять журнал и сверять исход; не создавать новую операцию без проверки результата
409 IDEMPOTENCY_RESULT_EXPIRED Получить operation_id из ошибки, проверить журнал и текущие ресурсы; ключ остаётся занятым
412 VERSION_MISMATCH Прочитать состояние, согласовать изменение; новое действие получает новый ключ
Ошибка проверки данных до принятия Исправить данные; без сохранённых изменений и записи об операции операция не была принята

При повторе запроса проверяется текущий доступ ко всем данным сохранённого результата. Если доступ утрачен, исходное тело не выдаётся частично: возможны 401/403/404. Ротация секрета подключения сама по себе не освобождает ключ и не меняет запись об операции.

Полное тело ответа хранится 24 часа. После его очистки запись об операции сохраняется: тот же ключ не позволяет выполнить действие заново. У асинхронной операции сроки хранения ответа о принятии и окончательного результата отсчитываются отдельно. При повторе запроса версии data.version и предупреждения из сохранённого ответа не пересчитываются. external_id дополняет эту защиту, но не заменяет её.

If-Resource-Version

Из GET карточки или списка скопируйте data.version и передайте без кавычек в If-Resource-Version. Сравнивается версия конкретной записи, а не глобальный номер состояния. Отсутствие обязательной версии даёт 428 VERSION_REQUIRED, неверный формат — 422 INVALID_VERSION, устаревшая версия — 412 VERSION_MISMATCH.

Заголовок содержит одно значение до 128 ASCII-символов из букв, цифр, ., _, ~, -. Кавычки, пробелы, *, W/, запятые и несколько значений не принимаются. В текущей реализации публичная версия — строка Unix-секунд. Клиент сравнивает версии только на равенство и не вычисляет их самостоятельно.

Команда Владелец версии
Создание самостоятельной сущности Заголовок не нужен
Изменение, переход, удаление карточки Карточка из пути
Добавление работы, товара, оборудования в акт Акт; для существующих единиц также expected_versions
Изменение/удаление строки акта Сама строка; состояние родительского акта проверяется отдельно
Добавление пункта оборудования/сценария, позиции комплекта, варианта поля Соответствующий родитель
Изменение/удаление дочернего пункта Дочерняя карточка
Загрузка вложения акта/выезда По схеме загрузки, без дополнительного заголовка версии родителя
Установка/замена фото оборудования (kind=photo) Оборудование, включая первую фотографию
Добавление документа оборудования (kind=document) Заголовок версии не нужен
Удаление вложения JSON-карточка вложения
Создание выезда act_version в JSON из GET акта
schedule/assign/unschedule и отметки выезда Выезд
initialize/ручной пункт результата ТО Заявка ТО
Изменение пункта результата ТО Обязательная целая version в JSON, без If-Resource-Version
execution заявки ТО Версия заявки в заголовке и целые версии переданных пунктов в JSON
Групповое plan-visit expected_versions каждого выбранного оборудования или заявки, без общего заголовка
Показание наработки Заголовок не требуется
Рабочие окна и исключения сотрудника Агрегат графика; версия одинакова для разных периодов GET

Полные исключения, владельцы версий маршрута и условия составной команды перечислены у ресурса. Лишний If-Resource-Version у команды без такого предусловия даёт UNEXPECTED_RESOURCE_VERSION. POST не поддерживает If-Match, If-None-Match, If-Unmodified-Since: их наличие даёт UNSUPPORTED_CONDITIONAL_HEADER.

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

Секундная точность updated_at

data.version — строка целого числа Unix-секунд сохранённого updated_at. Например, updated_at="2026-11-04T09:00:00Z" соответствует version="1793782800". Для старого updated_at=null используется "0". Сам updated_at, если предусмотрен ресурсом, возвращается в UTC с точностью до секунды; null сохраняется.

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

Практические последствия:

  • Читайте актуальную карточку перед изменением и выполняйте собственные записи одного ресурса последовательно. Это снижает риск, но не исключает одновременное изменение другим пользователем в ту же секунду.
  • Не используйте version как монотонный счётчик, порядок событий или доказательство отсутствия изменений.
  • При синхронизации применяйте полученное актуальное состояние даже при прежней версии. Для перечитывания используйте события, перекрывающиеся интервалы и полную сверку.
  • updated_at и meta.server_time не являются моментом завершения транзакции и не доказывают полноту уже видимых изменений. Долгая транзакция может стать видимой после прохода с более ранним маркером.
  • Если рабочий процесс требует обнаружить каждое конкурентное изменение или сохранить каждое промежуточное состояние, такой гарантии текущий контракт не предоставляет. Искусственная задержка клиента не превращает секундный маркер в строгую ревизию.

ETag представления

ETag — отдельный сильный валидатор конкретного GET-представления. Он не равен data.version и не используется для команд. Карточки изменяемых ресурсов выдают ETag; у других GET его наличие определяется операцией. Изменение данных, действующих прав или варианта кодирования может изменить ETag при прежней версии.

Передайте полученный тег с кавычками в If-None-Match. После текущей авторизации совпадение даёт 304 без тела; несовпадение — 200 с актуальным JSON. У 304 остаются ETag, новый X-Request-Id, Cache-Control и Vary. Отозванный доступ не превращается в 304. Ответы JSON GET используют Cache-Control: private, no-cache, Vary учитывает авторизацию и кодирование.

curl --silent --show-error --include \
  --header "Authorization: Bearer $SERVICEAPP_TOKEN" \
  --header 'If-None-Match: "<ETAG_FROM_PREVIOUS_GET>"' \
  "$API/customers/$CUSTOMER_ID"

Поддерживаются identity и gzip. Если клиент запрещает оба, возвращается 406 NOT_ACCEPTABLE. POST и повторные запросы не возвращают ETag; актуальное представление после команды читается отдельным GET.

Асинхронные операции и журнал

Ответ 202 подтверждает принятие и сохранение запроса. Выполнение ещё может продолжаться. Обычно тело содержит data={operation_id,state:"processing"}, тот же идентификатор в meta, а также заголовки Location и Retry-After: 2. Для файловых операций и расчёта маршрута структура data описана отдельно. Повтор принятого POST возвращает исходный ответ о принятии даже после завершения работы. Текущее состояние читайте в GET /api-operations/{id} с разрешением api_operations:read.

state Значение
processing Выполнение продолжается; опрос состояния возвращает 200 и Retry-After: 2
succeeded Подтверждённый успех; http_status и сохранённый response доступны до очистки тела
failed Подтверждённый сбой; изучите error и items, когда они есть
needs_reconciliation Нужна сверка исхода; автоматическое повторное выполнение прекращается

Для processing/needs_reconciliation финальные http_status и response равны null. response_available=false означает, что тела нет; это не разрешение выполнить команду повторно. Пакет может сохранить подтверждённые результаты отдельных items и общий failed/needs_reconciliation. Проверяйте каждый элемент, не объявляйте весь пакет успешным по наличию части результатов.

Проверяйте состояние с интервалом не меньше указанного в Retry-After; при длительном ожидании увеличивайте интервал. GET журнала только читает состояние и не запускает работу заново. Если первый ответ потерян и идентификатор операции неизвестен, повторите исходный POST без изменений. Список журнала по умолчанию охватывает последние 30 дней; максимальный период — 366 дней. Поля журнала.

Расчёт маршрутов имеет явные команды advance, apply, cancel. Опрос состояния карточки или журнала сам эти команды не исполняет. Используйте контракт маршрутов, где указаны версии шага, бюджет и окончательное применение. Для большого парка регламента используйте контракт регламентов.