Фото и документы оборудования
Все операции. Авторизация. Запросы и повторы.
Все пути в этом файле дополняют /api/v1 на домене организации.
Описание и ограничения
Поля
| Публичное поле | Формат и доступ |
|---|---|
id |
целое, только чтение |
equipment_id |
целое, только чтение; задаётся путём |
kind |
photo / document, входное поле/только чтение |
filename |
безопасное имя без пути, только чтение |
mime_type |
проверенный MIME, только чтение |
size |
целое число байтов, только чтение |
created_at |
ISO 8601 с поясом, только чтение |
version |
непрозрачная строка, только чтение |
download_url |
относительный путь на том же домене организации, только чтение |
file |
ровно один файл, входное поле |
client_filename |
необязательное имя файла, входное поле |
kind |
Поведение |
|---|---|
| photo | не более одного основного фото; новый файл заменяет прежний |
| document | независимые документы, включая изображения схем и паспортов |
Клиент передаёт только поля, перечисленные в схеме загрузки. Вид файла определяется через kind, владелец — адресом запроса.
Доступ и выборка
Нужны equipment_media:read для чтения и equipment_media:write для записи, доступ к оборудованию и права «ТОиР — Оборудование → Просмотр» или «ТОиР — Оборудование → Изменение» соответственно. Список, карточка, скачивание, журнал и повторные запросы проверяют текущий доступ к оборудованию, клиенту и объекту, а также подписку и модуль.
Каждый файл проверяется на принадлежность доступному оборудованию. Файл другого оборудования или организации возвращает 404. При скачивании заново проверяется токен; ранее полученный download_url не сохраняет доступ после отзыва токена.
Список поддерживает filter[kind]=photo или document, sort=id/-id и общую пагинацию по курсору. Без фильтра возвращаются оба вида. updated_since и другие фильтры не обещаны. Версия каждого элемента доступна в списке и отдельной JSON-карточке; GET карточки дополнительно возвращает ETag своего представления без скачивания файла.
Загрузка и замена фото
Для загрузки используйте multipart/form-data с полями file и kind. Необязательный client_filename задаёт имя файла до 255 символов без управляющих символов и разделителей пути. Если он не передан, берётся имя загружаемого файла без пути. Явно заданное имя с путём отклоняется. Ссылка, строка base64, путь на сервере или идентификатор другого файла не заменяют загрузку содержимого.
Один запрос принимает один файл размером от 1 до 10 МиБ включительно (10 485 760 байт). Неизвестные поля, повторяющиеся части и несколько файлов возвращают 422; превышение размера — 413 PAYLOAD_TOO_LARGE; неподдерживаемый Content-Type запроса — 415 UNSUPPORTED_MEDIA_TYPE.
kind |
Разрешённые типы MIME |
|---|---|
| photo | image/jpeg, image/png, image/webp, image/gif |
| document | application/pdf, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, image/jpeg, image/png, image/webp, image/gif |
Тип определяется по содержимому и формату, включая Office-контейнер. Заголовок клиента и расширение не являются единственной проверкой; неподдерживаемый или несогласованный файл — 422 FILE_TYPE_NOT_ALLOWED. SVG, HTML, архивы и PowerPoint не входят в эти коллекции, даже если допустимы у вложений акта.
Для kind=photo секундный updated_at оборудования проверяется под блокировкой до замены. Замена через API или панель обновляет этот маркер. Если секунда изменилась, следующий запрос со старой версией получает 412. При совпадении секунды два запроса с разными ключами могут последовательно заменить фото друг друга; это принятое ограничение секундной версии. Одинаковый ключ возвращает исходную запись об операции до повторной проверки прежнего If-Resource-Version. Клиент явно выбирает photo и тем самым запрашивает замену основного фото, а не добавление второго основного снимка.
Существующее фото остаётся доступным до готовности нового. При неуспешной загрузке или ошибочной версии прежнее фото сохраняется. После успешной замены оно становится недоступно через API; дальнейшее удаление старого файла не затрагивает новое фото.
Успех — 201 с карточкой нового файла, data.version, meta.operation_id, meta.affected_resources и meta.warnings=[], без ETag. Замена фото также создаёт новый идентификатор файла. В meta.affected_resources входят новый файл, прежний файл с deleted=true и version=null при замене, а также оборудование с актуальной версией. Добавление документа тоже обновляет версию оборудования. Если запрос принят, но файл ещё не готов, возвращается 202. До завершения проверяйте журнал операции.
{
"data": {
"id": 92,
"equipment_id": 42,
"kind": "photo",
"filename": "Шильдик.jpg",
"mime_type": "image/jpeg",
"size": 131072,
"created_at": "2026-10-30T09:00:00.000000Z",
"version": "1793782800",
"download_url": "/api/v1/equipment/42/media/92/download"
},
"meta": {
"operation_id": "op-example-photo-92",
"affected_resources": [
{"type": "equipment-media", "id": 92, "version": "1793782800", "deleted": false},
{"type": "equipment-media", "id": 91, "version": null, "deleted": true},
{"type": "equipment", "id": 42, "version": "1793782800", "deleted": false}
],
"warnings": []
}
}
Location у POST указывает на журнал операции. GET метаданных возвращает актуальные data и отдельный ETag представления, без operation_id принятого POST. Каждый ответ, включая ошибку, получает свой X-Request-Id только в заголовке.
Скачивание и удаление
Скачивание возвращает содержимое файла с кодом 200 с проверенным Content-Type, безопасным Content-Disposition: attachment, Cache-Control: private, no-store, X-Content-Type-Options: nosniff и X-Request-Id. JSON-обёртки у бинарного успеха нет. Версию для удаления берут из data.version JSON-метаданных, не из содержимого и не из ETag. Ошибки до начала передачи используют общий JSON-формат. Прямой путь к диску и постоянный публичный URL не выдаются.
Чтение списка, карточки и содержимого файла не создаёт публичную ссылку и не публикует паспорт оборудования. Загрузка не меняет настройки публичного доступа; фото в уже опубликованном паспорте отображается по действующим настройкам.
Удаление принимает {} с If-Resource-Version метаданных файла по секундному updated_at файла; текущее состояние владельца проверяется отдельно под блокировкой. Успех — 200, data={"id":92,"deleted":true}, operation_id и affected_resources: удалённая equipment-media с version=null и оборудование с актуальной version после обновления updated_at. Удаление основного фото оставляет коллекцию пустой; сервер не выбирает документ новым фото автоматически. Старый media_id после замены не может удалить новое фото. Повтор принятого удаления воспроизводит запись об операции; новая команда к отсутствующему ID возвращает 404.
Идемпотентность и восстановление
При повторе загрузки сохраняйте метод, путь, kind, итоговый client_filename, содержимое файла и условия проверки версии. Разделитель частей запроса не влияет на сравнение. Изменение файла, вида, имени, родителя или версии при том же ключе принятой операции возвращает 409 IDEMPOTENCY_CONFLICT.
После сбоя сервер продолжает исходную загрузку или замену, не создавая вторую запись. Принятое удаление сразу закрывает доступ к файлу через API, даже если физическое удаление ещё продолжается.
Повтор загрузки возвращает исходные ID, версию и affected_resources. Если фото позднее заменили ещё раз, повтор запроса прежней загрузки не возвращает его на место и не удаляет новое фото. Если документ позже удалили, повтор запроса не создаёт его заново. Отзыв прав запрещает сохранённый ответ; очистка тела записи об операции не разрешает повторное исполнение ключа.
Если версия оборудования изменилась после принятия фото, но до замены, операция получает состояние failed и ошибку 412 VERSION_MISMATCH. Старое фото сохраняется. Повтор с тем же ключом возвращает сохранённую ошибку; запрос с новой версией требует нового ключа. После смены клиента или объекта оборудования прежний сохранённый ответ недоступен и возвращается 404, даже если у подключения есть доступ к новому владельцу.
Операции
| Метод | Путь | Назначение |
|---|---|---|
| GET | /equipment/{equipment_id}/media |
Список метаданных основного фото и документов |
| POST | /equipment/{equipment_id}/media |
Добавление документа или установка основного фото, multipart/form-data |
| GET | /equipment/{equipment_id}/media/{id} |
JSON-карточка файла с version и сильным ETag |
| GET | /equipment/{equipment_id}/media/{id}/download |
Защищённое скачивание содержимого |
| POST | /equipment/{equipment_id}/media/{id}/delete |
Удаление файла, JSON {} |
GET /equipment/{equipment_id}/media
Список метаданных основного фото и документов.
Доступ. Разрешения подключения: equipment_media:read. Права сотрудника: «ТОиР — Оборудование → Просмотр». Дополнительно действуют доступ к записям, мастерским, модулям и условия полей из описания ресурса выше.
Параметры
| Имя | Где | Тип | Обязательно | null | Ограничения |
|---|---|---|---|---|---|
Authorization |
заголовок | строка | да | нет | Bearer <SERVICEAPP_TOKEN> |
equipment_id |
путь | строка | да | нет | Шаблон: ^(?:[0-9]+)$ |
filter[kind] |
параметр запроса | строка | нет | нет | Допустимо: "photo", "document" |
sort |
параметр запроса | строка | нет | нет | Допустимо: "id", "-id" |
page[size] |
параметр запроса | целое число | нет | нет | ≥ 1; ≤ 100 |
page[cursor] |
параметр запроса | строка | нет | нет | не более 8192 символов |
Ответы
| HTTP | Назначение и схема |
|---|---|
| 200 | Успех. Полная схема и пример |
| 401 | Требуется действующий Bearer. Коды и формат ошибки |
| 403 | Недостаточно допуска. Коды и формат ошибки |
| 404 | Не найдено или недоступно. Коды и формат ошибки |
| 422 | Ошибка входных данных. Коды и формат ошибки |
| 429 | Лимит запросов, Retry-After. Коды и формат ошибки |
| 503 | Временно недоступно. Коды и формат ошибки |
Каждый ответ содержит новый X-Request-Id в заголовке. Общие транспортные отказы и правила повтора действуют также при отказе до контроллера; см. ошибки.
Пример запроса
Запускается из каталога комплекта после настройки переменных в быстром старте. Замените демонстрационные идентификаторы 101 и UUID идентификаторами доступных записей. $RESOURCE_VERSION берётся из GET владельца версии, описанного выше. Для нового действия сохраните новый ключ; для повтора используйте прежний.
curl --silent --show-error --globoff \
"$API/equipment/101/media" \
--header "Authorization: Bearer $SERVICEAPP_TOKEN" \
--header "Accept: application/json"
POST /equipment/{equipment_id}/media
Добавление документа или установка основного фото, multipart/form-data.
Доступ. Разрешения подключения: equipment_media:write. Права сотрудника: «ТОиР — Оборудование → Изменение». Дополнительно действуют доступ к записям, мастерским, модулям и условия полей из описания ресурса выше.
Параметры
| Имя | Где | Тип | Обязательно | null | Ограничения |
|---|---|---|---|---|---|
Authorization |
заголовок | строка | да | нет | Bearer <SERVICEAPP_TOKEN> |
equipment_id |
путь | строка | да | нет | Шаблон: ^(?:[0-9]+)$ |
Idempotency-Key |
заголовок | строка | да | нет | не менее 1 символов; не более 128 символов; Шаблон: ^[A-Za-z0-9._:-]+$ |
If-Resource-Version |
заголовок | строка | нет | нет | Обязателен для kind=photo: версия оборудования.; не менее 1 символов; не более 128 символов; Шаблон: ^[A-Za-z0-9._~-]+$ |
Тело запроса
Content-Type: multipart/form-data. Тело обязательно.
| Поле | Тип | Обязательно | null | Ограничения и смысл |
|---|---|---|---|---|
kind |
строка | да | нет | Допустимо: "photo", "document" |
file |
строка (двоичные данные) | да | нет | Файл от 1 до 10 МиБ включительно; MIME проверяется по содержимому. |
client_filename |
строка | нет | нет | не более 255 символов |
Ответы
| HTTP | Назначение и схема |
|---|---|
| 201 | Создано. Полная схема и пример |
| 202 | Принято, итог в журнале. Полная схема и пример |
| 401 | Требуется действующий Bearer. Коды и формат ошибки |
| 403 | Недостаточно допуска. Коды и формат ошибки |
| 404 | Не найдено или недоступно. Коды и формат ошибки |
| 409 | Конфликт состояния или повтора. Коды и формат ошибки |
| 412 | Версия устарела. Коды и формат ошибки |
| 413 | Превышен размер. Коды и формат ошибки |
| 415 | Неподдерживаемый Content-Type. Коды и формат ошибки |
| 422 | Ошибка входных данных. Коды и формат ошибки |
| 428 | Нужно предусловие версии. Коды и формат ошибки |
| 429 | Лимит запросов, Retry-After. Коды и формат ошибки |
| 503 | Временно недоступно. Коды и формат ошибки |
| HTTP | Заголовок ответа | Тип | Описание |
|---|---|---|---|
| 201 | Location |
строка | |
| 202 | Location |
строка |
Каждый ответ содержит новый X-Request-Id в заголовке. Общие транспортные отказы и правила повтора действуют также при отказе до контроллера; см. ошибки.
У принятого POST Location и meta.operation_id указывают на журнал операции; ETag отсутствует. При повторе запроса сохраняются исходные data/meta, добавляется Idempotency-Replayed: true. Версии передавайте по назначению, указанному в описании ресурса.
Пример запроса
Запускается из каталога комплекта после настройки переменных в быстром старте. Замените демонстрационные идентификаторы 101 и UUID идентификаторами доступных записей. $RESOURCE_VERSION берётся из GET владельца версии, описанного выше. Для нового действия сохраните новый ключ; для повтора используйте прежний.
curl --silent --show-error --globoff --request POST \
"$API/equipment/101/media" \
--header "Authorization: Bearer $SERVICEAPP_TOKEN" \
--header "Accept: application/json" \
--header "Idempotency-Key: example:v1-equipment-media-store:001" \
--form 'kind=document' \
--form 'file=@./examples/attachment.pdf;type=application/pdf'
GET /equipment/{equipment_id}/media/{id}
JSON-карточка файла с version и сильным ETag.
Доступ. Разрешения подключения: equipment_media:read. Права сотрудника: «ТОиР — Оборудование → Просмотр». Дополнительно действуют доступ к записям, мастерским, модулям и условия полей из описания ресурса выше.
Параметры
| Имя | Где | Тип | Обязательно | null | Ограничения |
|---|---|---|---|---|---|
Authorization |
заголовок | строка | да | нет | Bearer <SERVICEAPP_TOKEN> |
equipment_id |
путь | строка | да | нет | Шаблон: ^(?:[0-9]+)$ |
id |
путь | строка | да | нет | Шаблон: ^(?:[0-9]+)$ |
If-None-Match |
заголовок | строка | нет | нет | — |
Ответы
| HTTP | Назначение и схема |
|---|---|
| 200 | Успех. Полная схема и пример |
| 304 | Представление не изменилось, без тела. Условный GET |
| 401 | Требуется действующий Bearer. Коды и формат ошибки |
| 403 | Недостаточно допуска. Коды и формат ошибки |
| 404 | Не найдено или недоступно. Коды и формат ошибки |
| 422 | Ошибка входных данных. Коды и формат ошибки |
| 429 | Лимит запросов, Retry-After. Коды и формат ошибки |
| 503 | Временно недоступно. Коды и формат ошибки |
| HTTP | Заголовок ответа | Тип | Описание |
|---|---|---|---|
| 200 | ETag |
строка | |
| 304 | ETag |
строка |
Каждый ответ содержит новый X-Request-Id в заголовке. Общие транспортные отказы и правила повтора действуют также при отказе до контроллера; см. ошибки.
Пример запроса
Запускается из каталога комплекта после настройки переменных в быстром старте. Замените демонстрационные идентификаторы 101 и UUID идентификаторами доступных записей. $RESOURCE_VERSION берётся из GET владельца версии, описанного выше. Для нового действия сохраните новый ключ; для повтора используйте прежний.
curl --silent --show-error --globoff \
"$API/equipment/101/media/101" \
--header "Authorization: Bearer $SERVICEAPP_TOKEN" \
--header "Accept: application/json"
GET /equipment/{equipment_id}/media/{id}/download
Защищённое скачивание содержимого.
Доступ. Разрешения подключения: equipment_media:read. Права сотрудника: «ТОиР — Оборудование → Просмотр». Дополнительно действуют доступ к записям, мастерским, модулям и условия полей из описания ресурса выше.
Параметры
| Имя | Где | Тип | Обязательно | null | Ограничения |
|---|---|---|---|---|---|
Authorization |
заголовок | строка | да | нет | Bearer <SERVICEAPP_TOKEN> |
equipment_id |
путь | строка | да | нет | Шаблон: ^(?:[0-9]+)$ |
id |
путь | строка | да | нет | Шаблон: ^(?:[0-9]+)$ |
Ответы
| HTTP | Назначение и схема |
|---|---|
| 200 | Успех. Бинарный файл без JSON-обёртки; вложения |
| 401 | Требуется действующий Bearer. Коды и формат ошибки |
| 403 | Недостаточно допуска. Коды и формат ошибки |
| 404 | Не найдено или недоступно. Коды и формат ошибки |
| 422 | Ошибка входных данных. Коды и формат ошибки |
| 429 | Лимит запросов, Retry-After. Коды и формат ошибки |
| 503 | Временно недоступно. Коды и формат ошибки |
| HTTP | Заголовок ответа | Тип | Описание |
|---|---|---|---|
| 200 | Content-Disposition |
строка | |
| 200 | X-Content-Type-Options |
строка | |
| 200 | Cache-Control |
строка | |
| 401 | Cache-Control |
строка | |
| 403 | Cache-Control |
строка | |
| 404 | Cache-Control |
строка | |
| 422 | Cache-Control |
строка | |
| 429 | Cache-Control |
строка | |
| 503 | Cache-Control |
строка |
Каждый ответ содержит новый X-Request-Id в заголовке. Общие транспортные отказы и правила повтора действуют также при отказе до контроллера; см. ошибки.
Пример запроса
Запускается из каталога комплекта после настройки переменных в быстром старте. Замените демонстрационные идентификаторы 101 и UUID идентификаторами доступных записей. $RESOURCE_VERSION берётся из GET владельца версии, описанного выше. Для нового действия сохраните новый ключ; для повтора используйте прежний.
curl --silent --show-error --globoff \
"$API/equipment/101/media/101/download" \
--header "Authorization: Bearer $SERVICEAPP_TOKEN" \
--header "Accept: application/json"
POST /equipment/{equipment_id}/media/{id}/delete
Удаление файла, JSON {}.
Доступ. Разрешения подключения: equipment_media:write. Права сотрудника: «ТОиР — Оборудование → Изменение». Дополнительно действуют доступ к записям, мастерским, модулям и условия полей из описания ресурса выше.
Параметры
| Имя | Где | Тип | Обязательно | null | Ограничения |
|---|---|---|---|---|---|
Authorization |
заголовок | строка | да | нет | Bearer <SERVICEAPP_TOKEN> |
equipment_id |
путь | строка | да | нет | Шаблон: ^(?:[0-9]+)$ |
id |
путь | строка | да | нет | Шаблон: ^(?:[0-9]+)$ |
Idempotency-Key |
заголовок | строка | да | нет | не менее 1 символов; не более 128 символов; Шаблон: ^[A-Za-z0-9._:-]+$ |
If-Resource-Version |
заголовок | строка | да | нет | не менее 1 символов; не более 128 символов; Шаблон: ^[A-Za-z0-9._~-]+$ |
Тело запроса
Content-Type: application/json. Тело обязательно.
Тело — пустой JSON-объект {}. Другие ключи не принимаются.
Ответы
| HTTP | Назначение и схема |
|---|---|
| 200 | Успех. Полная схема и пример |
| 401 | Требуется действующий Bearer. Коды и формат ошибки |
| 403 | Недостаточно допуска. Коды и формат ошибки |
| 404 | Не найдено или недоступно. Коды и формат ошибки |
| 409 | Конфликт состояния или повтора. Коды и формат ошибки |
| 412 | Версия устарела. Коды и формат ошибки |
| 422 | Ошибка входных данных. Коды и формат ошибки |
| 428 | Нужно предусловие версии. Коды и формат ошибки |
| 429 | Лимит запросов, Retry-After. Коды и формат ошибки |
| 503 | Временно недоступно. Коды и формат ошибки |
| HTTP | Заголовок ответа | Тип | Описание |
|---|---|---|---|
| 200 | Location |
строка |
Каждый ответ содержит новый X-Request-Id в заголовке. Общие транспортные отказы и правила повтора действуют также при отказе до контроллера; см. ошибки.
У принятого POST Location и meta.operation_id указывают на журнал операции; ETag отсутствует. При повторе запроса сохраняются исходные data/meta, добавляется Idempotency-Replayed: true. Версии передавайте по назначению, указанному в описании ресурса.
Пример запроса
Запускается из каталога комплекта после настройки переменных в быстром старте. Замените демонстрационные идентификаторы 101 и UUID идентификаторами доступных записей. $RESOURCE_VERSION берётся из GET владельца версии, описанного выше. Для нового действия сохраните новый ключ; для повтора используйте прежний.
curl --silent --show-error --globoff --request POST \
"$API/equipment/101/media/101/delete" \
--header "Authorization: Bearer $SERVICEAPP_TOKEN" \
--header "Accept: application/json" \
--header "Idempotency-Key: example:v1-equipment-media-destroy:001" \
--header "If-Resource-Version: $RESOURCE_VERSION" \
--header "Content-Type: application/json" \
--data-binary '@examples/requests/v1.equipment-media.destroy.json'