Справочник API OKTO¶
Полный справочник REST и WebSocket API центрального локального сервера (далее — ЦЛС) и граничного edge-сервиса. Для каждого эндпоинта: путь, роль, параметры, тело, пример ответа, коды ошибок.
Аудитория: интеграторы, авторы автоматизаций, разработчики клиентов. Связанные документы: SERVER_MANAGEMENT.ru.md, ARCHITECTURE.ru.md, DEPLOYMENT.ru.md.
Содержание¶
- Базовые URL и версионирование
- Формат ответа
- Аутентификация
- Коды ошибок
- Health и version
- Auth — /api/v1/auth/*
- Users — /api/v1/users/*
- Terminals — /api/v1/terminals/*
- Devices — /api/v1/devices/*
- Device commands — /api/v1/devices/{id}/commands
- Device groups — /api/v1/device-groups/*
- Firmware — /api/v1/firmware/*
- Dashboard — /api/v1/dashboard/*
- Sync (для edge) — /api/v1/sync
- Alerts — /api/v1/alerts/*
- Audit log — /api/v1/audit-log
- Connection mode — /api/v1/config/connection-mode
- WebSocket: /ws/device
- WebSocket: /ws/dashboard
- Edge REST API (локальный, для операторского UI)
1. Базовые URL и версионирование¶
| Сервис | Дефолт URL | Порт |
|---|---|---|
| Центральный локальный сервер | https://<factory-host>/api/v1 |
8081 |
| Edge-сервис (на устройстве) | http://<edge-host>/api/v1 |
8080 |
Все REST-эндпоинты находятся под префиксом /api/v1. При breaking changes вводится /api/v2, старая версия сохраняется как минимум один мажор.
2. Формат ответа¶
Успех:
{
"success": true,
"data": { /* полезная нагрузка */ },
"error": null,
"meta": { "total": 100, "limit": 50, "offset": 0 }
}
Ошибка:
{
"success": false,
"data": null,
"error": {
"code": "DEVICE_NOT_FOUND",
"message": "Device edge-99 does not exist",
"details": { "identifier": "edge-99" }
},
"meta": null
}
Поле meta опционально и заполняется только для list-эндпоинтов с пагинацией.
3. Аутентификация¶
Все приватные эндпоинты ожидают заголовок:
- User JWT выдаётся через
POST /auth/login. Claims:sub=userId,scope=user,role. - Device JWT выдаётся через
POST /devices/{id}/token+X-Enrollment-Key. Claims:sub=deviceId,scope=device.
WS-эндпоинты принимают JWT через query-string ?token=<JWT>.
Legacy HMAC session tokens (старый формат из AuthService.createSession) валидны до истечения, но новые клиенты должны использовать JWT.
4. Коды ошибок¶
| HTTP | error.code |
Значение |
|---|---|---|
| 400 | INVALID_PAYLOAD |
JSON невалиден / не соответствует схеме |
| 400 | MISSING_PARAM |
Отсутствует обязательный query/form-параметр |
| 400 | VALIDATION_ERROR |
Нарушены бизнес-правила (длина, формат, уникальность) |
| 401 | UNAUTHORIZED |
Нет токена или он не прошёл проверку |
| 401 | INVALID_ENROLLMENT_KEY |
X-Enrollment-Key не совпал |
| 403 | FORBIDDEN |
Роль не имеет права на это действие |
| 403 | DEVICE_NOT_REGISTERED |
Auto-enrollment выключен, устройство незнакомо |
| 404 | NOT_FOUND |
Общий 404 |
| 404 | DEVICE_NOT_FOUND |
Нет устройства с таким identifier |
| 404 | RELEASE_NOT_FOUND |
Нет прошивки с таким id |
| 409 | CONFLICT |
Конкурентное обновление (optimistic lock) |
| 409 | DEVICE_DISABLED |
Устройство в disabled=true — команда запрещена |
| 413 | PAYLOAD_TOO_LARGE |
Artifact превышает firmware.maxArtifactSizeBytes |
| 422 | UNPROCESSABLE |
Запрос валиден синтаксически, но не может быть выполнен |
| 429 | RATE_LIMITED |
Слишком частые вызовы (добавить rate-limit плагин) |
| 500 | INTERNAL_ERROR |
Необработанное исключение |
| 502 | CLOUD_UPSTREAM_ERROR |
OKTO Cloud недоступен |
| 503 | SERVICE_UNAVAILABLE |
ЦЛС в режиме maintenance или БД недоступна |
5. Health и version¶
GET /health¶
Без аутентификации. Возвращает 200 если Ktor жив.
GET /api/v1/health¶
С проверкой БД. 200 если ok, 503 если БД недоступна.
GET /api/v1/version¶
{
"success": true,
"data": {
"service": "factory-server",
"version": "1.2.3",
"gitSha": "abc1234",
"buildTimestamp": "2026-04-17T18:42:00Z"
}
}
6. Auth — /api/v1/auth/*¶
POST /auth/login¶
Публичный. Возвращает JWT + сессию (для легаси).
Request:
Response 200:
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIs…",
"sessionToken": "…",
"user": { "id": "…", "username": "admin", "role": "ADMIN", "disabled": false }
}
}
Ошибки:
- 401 INVALID_CREDENTIALS
- 403 USER_DISABLED
- 429 RATE_LIMITED
Аудит: LOGIN_SUCCESS / LOGIN_FAILED.
POST /auth/logout¶
Приватный. Инвалидирует session row. JWT остаётся валидным до exp (ограничение).
GET /auth/me¶
Приватный. Возвращает текущего пользователя.
{
"success": true,
"data": { "id": "…", "username": "admin", "role": "ADMIN", "disabled": false, "lastLoginAt": "…" }
}
POST /auth/change-password¶
Приватный. Требует текущий пароль.
7. Users — /api/v1/users/*¶
Все операции — роль ADMIN (кроме GET /users — MANAGER+).
GET /users¶
Список пользователей. Фильтры: role, disabled, q (search по username).
{
"success": true,
"data": [
{ "id": "u-1", "username": "admin", "role": "ADMIN", "disabled": false, "createdAt": "…", "lastLoginAt": "…" }
],
"meta": { "total": 3, "limit": 50, "offset": 0 }
}
POST /users¶
Валидация:
- username уникален, длина 3–32, ASCII.
- password ≥ 8 символов.
- role ∈ enum UserRole.
GET /users/{id}¶
PUT /users/{id}¶
DELETE /users/{id}¶
Soft delete: disabled=true (данные не удаляются для аудита).
Аудит: USER_CREATED / USER_ROLE_CHANGED / USER_DELETED.
8. Terminals — /api/v1/terminals/*¶
Терминалы — логические точки доступа (обычно соответствуют edge-устройствам). Используются для scope-ирования пользователя.
GET /terminals — список
POST /terminals — создать
GET /terminals/{id} — детали
PUT /terminals/{id} — обновить
DELETE /terminals/{id} — удалить
POST /terminals/{id}/heartbeat — heartbeat от терминала
Поля:
9. Devices — /api/v1/devices/*¶
GET /devices¶
Список зарегистрированных устройств. Фильтры:
| Параметр | Тип | Описание |
|---|---|---|
status |
ONLINE,OFFLINE,ERROR,MAINTENANCE |
Множественный |
companyId |
string | |
productionLineId |
string | |
connectionMode |
DIRECT_CLOUD,VIA_LOCAL_SERVER |
|
enabled |
bool | |
groupId |
string | |
q |
string | Подстрочный поиск по name/identifier |
limit, offset |
Пагинация |
Response:
{
"success": true,
"data": [
{
"identifier": "edge-01",
"name": "Line 1 terminal",
"type": "EDGE",
"status": "ONLINE",
"companyId": "acme",
"productionLineId": "line-1",
"connectionMode": "VIA_LOCAL_SERVER",
"connectionModeOverride": false,
"enabled": true,
"groupId": "grp-eu",
"firmwareVersion": "1.2.3",
"version": "1.2.3",
"ipAddress": "10.0.1.23",
"lastHeartbeat": "2026-04-17T22:15:43Z",
"lastCommandAt": "2026-04-17T22:14:11Z",
"createdAt": "2026-01-10T12:00:00Z"
}
],
"meta": { "total": 14, "limit": 50, "offset": 0 }
}
POST /devices¶
Ручная регистрация (без auto-enrollment).
{
"identifier": "edge-05",
"name": "Spare terminal",
"companyId": "acme",
"productionLineId": "line-5",
"connectionMode": "VIA_LOCAL_SERVER"
}
GET /devices/{id}¶
Полные детали устройства.
PUT /devices/{id}¶
Обновление (имя, группа, enabled).
DELETE /devices/{id}¶
Удаление. Связанные aggregated_* строки остаются (soft unregister).
POST /devices/{id}/token¶
Публичный endpoint с X-Enrollment-Key. См. SERVER_MANAGEMENT.ru.md §3.
POST /devices/{id}/heartbeat¶
Edge-устройство шлёт heartbeat с метриками (альтернатива WS StatusEvent — используется в DIRECT_CLOUD режиме).
{
"deviceId": "edge-01",
"status": "ONLINE",
"timestamp": "2026-04-17T22:15:43Z",
"ipAddress": "10.0.1.23",
"version": "1.2.3",
"metrics": {
"cpuUsage": 42.5,
"memoryUsage": 61.2,
"diskUsage": 28.4,
"offlineQueueDepth": 2,
"bottlesProcessedLastHour": 1380,
"errorsLastHour": 0
}
}
Сохраняется в devices.last_heartbeat + device_metrics (если метрики поданы).
GET /devices/{id}/metrics¶
История метрик.
Параметры: since, until, resolution=5m (агрегация).
{
"success": true,
"data": [
{ "timestamp": "2026-04-17T22:00:00Z", "cpuUsage": 38.2, "memoryUsage": 58.1, "offlineQueueDepth": 1 }
]
}
GET /devices/connected¶
Моментальный срез WS-сессий:
{
"success": true,
"data": [
{ "deviceId": "edge-01", "connectedAt": "…", "lastSeen": "…", "firmwareVersion": "1.2.3" }
]
}
GET /devices/{id}/status, GET /devices/{id}/history¶
Зарезервированы для кастомных интеграций (статус за 24 ч, история подключений).
10. Device commands¶
POST /devices/{id}/commands¶
См. детали в SERVER_MANAGEMENT.ru.md §5.
GET /devices/{id}/commands¶
Фильтры: status, type, createdBy, since, until, limit, offset.
GET /devices/{id}/commands/{cmdId}¶
GET /devices/{id}/logs¶
История логов конкретного устройства.
Поля: id, deviceId, commandId?, ts, level, logger, line.
GET /devices/{id}/config¶
{
"success": true,
"data": {
"deviceId": "edge-01",
"configJson": "{\"scanner\":{\"timeoutMs\":5000}}",
"version": 3,
"updatedAt": "…",
"updatedBy": "u-admin"
}
}
PUT /devices/{id}/config¶
version— optimistic lock (если не совпадает с текущим — 409).autoDispatch: true→ сервер сразу шлётPushConfigCmdустройству.
11. Device groups¶
GET /device-groups¶
{
"success": true,
"data": [
{ "id": "grp-eu", "name": "EU terminals", "description": "…", "memberCount": 12, "createdAt": "…" }
]
}
POST /device-groups¶
GET /device-groups/{id}¶
Возвращает детали + участников.
PUT /device-groups/{id}¶
DELETE /device-groups/{id}¶
GET /device-groups/{id}/members¶
Список deviceId + опционально сами устройства.
POST /device-groups/{id}/members¶
DELETE /device-groups/{id}/members¶
POST /device-groups/{id}/commands¶
Bulk dispatch. См. SERVER_MANAGEMENT.ru.md §6.
12. Firmware — /api/v1/firmware/*¶
POST /firmware/releases¶
Upload. Content-Type: application/octet-stream. Параметры в query:
| Param | Required | Description |
|---|---|---|
version |
✓ | Уникальная версия (SemVer рекомендовано) |
channel |
stable (default) / beta / canary |
|
filename |
Имя файла, default firmware.jar |
|
notes |
Release notes | |
signatureBase64 |
Ed25519 signature (если включена проверка) |
Response 200:
{
"success": true,
"data": {
"id": "73e395e6-…",
"version": "1.2.3",
"channel": "stable",
"artifactUrl": "/api/v1/firmware/releases/73e395e6-…/artifact",
"sha256": "d5e5a02b…",
"sizeBytes": 12345678,
"releaseNotes": "…",
"createdAt": "…",
"createdBy": "u-admin"
}
}
GET /firmware/releases¶
Список. Фильтры: channel, since, until.
GET /firmware/releases/{id}¶
DELETE /firmware/releases/{id}¶
Удаляет метаданные и артефакт на диске. Деплои со ссылкой на релиз остаются (для аудита), но url становится невалидным.
GET /firmware/releases/{id}/artifact¶
Скачивание. Стримит application/octet-stream. Проверяет Authorization (user или device JWT).
POST /firmware/deployments¶
Альтернативно { "releaseId": "…", "groupId": "grp-eu" }.
Response: Map<deviceId, CommandResult>.
GET /firmware/deployments¶
Фильтры: releaseId, deviceId, status.
{
"success": true,
"data": [
{ "id": "dep-…", "releaseId": "73e395e6-…", "deviceId": "edge-01", "status": "SUCCESS", "startedAt": "…", "completedAt": "…", "errorMessage": null }
]
}
13. Dashboard — /api/v1/dashboard/*¶
GET /dashboard/overview¶
Сводка для главной страницы консоли.
{
"success": true,
"data": {
"onlineDevices": 12,
"totalDevices": 14,
"bottlesProcessedToday": 12854,
"batchesCreatedToday": 214,
"palletsCreatedToday": 18,
"cloudQueueDepth": 3,
"cloudQueueDeadLetters": 0,
"averageSyncLagSeconds": 2.1
}
}
GET /dashboard/production¶
Агрегаты по производственным линиям / компаниям.
{
"success": true,
"data": [
{ "productionLineId": "line-1", "bottles": 8214, "batches": 120, "pallets": 10 }
]
}
GET /dashboard/devices/summary¶
Short-form устройства (для табличного виджета).
GET /dashboard/sync/queue¶
Статистика cloud_sync_queue:
{
"success": true,
"data": {
"pendingCount": 3,
"inProgressCount": 1,
"failedCount": 0,
"deadLetterCount": 0,
"oldestPendingAgeSeconds": 14
}
}
14. Sync (для edge) — /api/v1/sync¶
Ручка, через которую edge-устройство (в режиме VIA_LOCAL_SERVER) отправляет накопленные операции.
Аутентификация — device JWT.
POST /api/v1/sync HTTP/1.1
Authorization: Bearer <deviceJwt>
Content-Type: application/json
{
"deviceId": "edge-01",
"timestamp": "2026-04-17T22:15:43Z",
"operations": [
{
"operationId": "op-…",
"operationType": "BOTTLE_CREATE",
"payload": { "bottle": { "identifier": "b-1", "exciseDutyNumber": "…", "productionLineId": "line-1" } },
"createdAt": "…"
}
]
}
Response:
{
"success": true,
"data": {
"accepted": ["op-1","op-2"],
"rejected": [ { "operationId": "op-3", "reason": "duplicate excise_duty_number" } ]
}
}
Отклонённые операции edge помечает как DONE (они либо дубликаты, либо нарушают инварианты и не подлежат retry).
15. Alerts — /api/v1/alerts/*¶
GET /alerts — список
GET /alerts/counts — агрегированный счёт по severity
GET /alerts/{id} — детали
POST /alerts/{id}/acknowledge — ACK
Поля алерта:
{
"id": 123,
"deviceId": "edge-01",
"severity": "WARN",
"category": "printer_offline",
"message": "Printer videojet-1 not responding",
"details": "…",
"acknowledged": false,
"acknowledgedBy": null,
"acknowledgedAt": null,
"createdAt": "…"
}
16. Audit log¶
Фильтры: userId, entityId, action, since, until, limit, offset.
{
"success": true,
"data": [
{
"id": 45123,
"actorUserId": "u-admin",
"action": "DEVICE_COMMAND_DISPATCHED",
"entityType": "device",
"entityId": "edge-01",
"ip": "10.0.1.5",
"userAgent": "Mozilla/5.0 …",
"metaJson": { "commandType": "force_sync", "commandId": "cmd-…" },
"ts": "2026-04-17T22:15:43Z"
}
],
"meta": { "total": 4521, "limit": 200, "offset": 0 }
}
17. Connection mode¶
GET /api/v1/config/connection-mode¶
{
"success": true,
"data": {
"settings": {
"defaultMode": "VIA_LOCAL_SERVER",
"allowDeviceOverride": true,
"cloudEndpoint": "https://app.okto.ru/api/v1",
"cloudConfigured": true
},
"stats": {
"totalDevices": 14,
"viaLocalServerCount": 12,
"directCloudCount": 2,
"withOverridesCount": 3
}
}
}
PUT /api/v1/config/connection-mode¶
{
"defaultMode": "VIA_LOCAL_SERVER",
"allowDeviceOverride": true,
"cloudEndpoint": "https://app.okto.ru/api/v1",
"cloudApiKey": "secret"
}
Переопределение для конкретного устройства¶
PUT /api/v1/devices/{id}/connection-mode
{ "mode": "DIRECT_CLOUD", "reason": "Manual override for testing" }
Очистка override¶
Устройство возвращается к глобальному дефолту.
18. WebSocket: /ws/device¶
Принимает device JWT в query-string: wss://<factory>/ws/device?token=<deviceJwt>.
После handshake:
- Устройство шлёт
DeviceHelloMessage { deviceId, version, bootTs }(первый кадр). - Сервер регистрирует в
DeviceConnectionRegistryи начинает слать команды. - Устройство присылает
StatusEvent,LogLineEvent,CommandResult,CommandProgress,DeviceScanEvent,DevicePrintEvent,DeviceAlertEvent. - Сервер шлёт PING каждые
management.heartbeatIntervalSeconds; устройство должно отвечать PONG.
Формат — JSON с полем type (classDiscriminator).
Примеры¶
Сервер → устройство (команда):
Устройство → сервер (результат):
{
"type": "cmd_result",
"commandId": "cmd-550e8400-e29b-41d4-a716",
"success": true,
"output": "Force-sync completed. In-progress: 7",
"error": null,
"exitCode": null,
"data": { "durationMs": 412 }
}
Устройство → сервер (heartbeat):
{
"type": "status",
"deviceId": "edge-01",
"status": "ONLINE",
"ts": "2026-04-17T22:15:43Z",
"metrics": { "cpuUsage": 38.1, "memoryUsage": 57.0, "offlineQueueDepth": 0 }
}
Close codes¶
| Код | Причина |
|---|---|
| 1000 | Нормальное закрытие |
| 1008 | Невалидный JWT или отсутствует токен |
| 1011 | Внутренняя ошибка сервера |
| 4000 | Не прислан DeviceHelloMessage в течение 5 секунд |
| 4001 | Device JWT истёк |
| 4002 | Device разрешил disabled=true и сервер закрыл сессию |
19. WebSocket: /ws/dashboard¶
Принимает user JWT в query-string: wss://<factory>/ws/dashboard?token=<userJwt>.
Subscribe¶
Первый кадр обязателен в течение 5 с:
{ "type": "subscribe", "deviceIds": ["edge-01"], "eventTypes": ["status","cmd_result","log_line","alert","scan"] }
Пустые массивы = получать всё. eventTypes ∈ {status, scan, print, alert, cmd_result, cmd_progress, log_line}.
Unsubscribe¶
Примеры входящих событий¶
{"type":"status","deviceId":"edge-01","status":"ONLINE","ts":"…","metrics":{…}}
{"type":"cmd_result","deviceId":"edge-01","commandId":"cmd-…","success":true,"output":"…","ts":"…"}
{"type":"log_line","deviceId":"edge-01","ts":"…","level":"ERROR","line":"…"}
{"type":"alert","deviceId":"edge-01","severity":"WARN","message":"Printer offline"}
Close codes¶
Аналогичны /ws/device. Дополнительно:
- 4003 — Timeout на первое subscribe-сообщение.
20. Edge REST API (локальный, для операторского UI)¶
Edge-сервис также поднимает REST на порту 8080 для оператора.
POST /api/v1/auth/login¶
Локальный логин оператора.
GET /api/v1/tasks¶
Список копакинг-заданий на линии.
POST /api/v1/bottles¶
Создать бутылку (обычно из сканера):
{
"bottles": [
{ "identifier": "b-1", "exciseDutyNumber": "0104650001234567211234pl01", "productionLineId": "line-1" }
],
"deviceId": "edge-01"
}
POST /api/v1/batches¶
Создать батч из N бутылок.
POST /api/v1/pallets¶
Создать паллету из M батчей.
POST /api/v1/batches/{id}/fixate¶
Финализировать временный батч.
GET /api/v1/hardware/status¶
Статус принтеров/сканеров/ПЛК.
GET /api/v1/queue/stats¶
Статистика локальной очереди:
{
"pendingCount": 12,
"inProgressCount": 1,
"doneCount": 8421,
"failedCount": 0,
"oldestPendingAgeSeconds": 3
}
POST /api/v1/sync/trigger¶
Мгновенный sync (эквивалент force_sync команды).
GET /api/v1/connection-mode¶
Текущий режим (DIRECT_CLOUD / VIA_LOCAL_SERVER).
PUT /api/v1/connection-mode¶
Смена режима (если allowDeviceOverride=true на сервере).
Обновлено: апрель 2026. Статус: активно поддерживается. Для вопросов — engineering@okto.ru.