OKTO Traceability System - API Reference¶
Complete API documentation for Edge Service and Factory Server.
Base URLs¶
| Service | Default URL |
|---|---|
| Edge Service | http://localhost:8080 |
| Factory Server | http://localhost:8081 |
Authentication¶
Local Authentication¶
Most endpoints require JWT authentication:
# Login to get token
curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "operator", "password": "operator123"}'
# Response
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "user-001",
"username": "operator",
"role": "OPERATOR"
}
}
}
# Use token in subsequent requests
curl http://localhost:8080/api/v1/bottles \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Cloud Authentication¶
For Direct Cloud mode, OKTO Cloud uses a two-token system:
- User Token - Obtained from
/sign_in, used for device selection - Device Token - Obtained from
/devices/{id}/activate, used for operations
Edge Service API¶
Health Check¶
GET /health¶
Check service health.
Response:
Authentication¶
POST /api/v1/auth/login¶
Authenticate user with local credentials.
Request:
Response:
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "user-001",
"username": "operator",
"role": "OPERATOR",
"displayName": "John Operator"
}
}
}
Provisioning (Direct Cloud Mode)¶
GET /api/v1/provision/status¶
Get current provisioning status.
Response:
{
"success": true,
"data": {
"status": "PROVISIONED",
"deviceId": "device-001",
"deviceIdentifier": "edge-001_1704067200",
"companyId": "company-001",
"companyName": "Demo Factory LLC",
"deviceName": "Production Line 1",
"seqNumber": "001",
"industry": "milk"
}
}
Status Values: NOT_PROVISIONED, SIGNING_IN, SIGNED_IN, ACTIVATING, PROVISIONED
POST /api/v1/provision/signin¶
Sign in with OKTO Cloud credentials.
Request:
Response:
GET /api/v1/provision/devices¶
Get available devices for activation.
Response:
{
"success": true,
"data": [
{
"id": "device-001",
"name": "Production Line 1",
"companyId": "company-001",
"companyName": "Demo Factory LLC",
"seqNumber": "001",
"deviceMode": "scan_bottles"
},
{
"id": "device-002",
"name": "Quality Control",
"companyId": "company-001",
"companyName": "Demo Factory LLC",
"seqNumber": "002",
"deviceMode": "verify"
}
]
}
POST /api/v1/provision/activate¶
Activate selected device.
Request:
{
"deviceId": "device-001",
"deviceName": "Production Line 1",
"companyId": "company-001",
"companyName": "Demo Factory LLC",
"seqNumber": "001",
"deviceMode": "scan_bottles"
}
Response:
{
"success": true,
"device": {
"deviceId": "device-001",
"deviceIdentifier": "edge-001_1704067200",
"companyId": "company-001",
"companyName": "Demo Factory LLC",
"deviceName": "Production Line 1",
"seqNumber": "001",
"industry": "milk"
},
"message": "Device activated successfully"
}
POST /api/v1/provision/deprovision¶
Reset device provisioning.
Response:
GET /api/v1/provision/device¶
Get provisioned device information.
Response:
{
"success": true,
"data": {
"deviceId": "device-001",
"deviceIdentifier": "edge-001_1704067200",
"companyId": "company-001",
"companyName": "Demo Factory LLC",
"deviceName": "Production Line 1",
"seqNumber": "001",
"industry": "milk",
"lastBatchIdentifier": "B-2024-001234",
"lastPalletIdentifier": "P-2024-000567"
}
}
Bottles¶
GET /api/v1/bottles¶
List bottles with optional filters.
Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | status | int | Filter by status (0-5) | | lineId | string | Filter by production line | | limit | int | Max results (default: 100) |
Example:
Response:
{
"success": true,
"data": [
{
"id": "bottle-001",
"identifier": "010460000000001721ABC123KRIPTO1234",
"gtin": "04600000000001",
"status": 0,
"createdAt": "2024-01-01T10:00:00Z",
"productionLineId": "line-001",
"party": "PARTY-001",
"batchId": null
}
]
}
Status Values: | Value | Meaning | |-------|---------| | 0 | Created | | 1 | In Batch | | 2 | Sent | | 3 | Synced | | 4 | Rejected | | 5 | Deleted |
POST /api/v1/bottles¶
Create bottles.
Request:
{
"bottles": [
{
"identifier": "010460000000001721ABC123KRIPTO1234",
"gtin": "04600000000001",
"productionLineId": "line-001",
"party": "PARTY-001"
}
],
"deviceId": "device-001"
}
Response:
{
"success": true,
"data": {
"created": 1,
"bottles": [
{
"id": "bottle-001",
"identifier": "010460000000001721ABC123KRIPTO1234"
}
]
}
}
GET /api/v1/bottles/{id}¶
Get bottle details.
Response:
{
"success": true,
"data": {
"id": "bottle-001",
"identifier": "010460000000001721ABC123KRIPTO1234",
"gtin": "04600000000001",
"exciseDutyNumber": "ABC123",
"status": 0,
"createdAt": "2024-01-01T10:00:00Z",
"updatedAt": "2024-01-01T10:00:00Z",
"productionLineId": "line-001",
"productId": "product-001",
"party": "PARTY-001",
"batchId": null,
"isPreprint": false
}
}
GET /api/v1/bottles/count¶
Get bottle counts.
Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | status | int | Filter by status |
Response:
PATCH /api/v1/bottles/{id}/status¶
Update bottle status.
Request:
Response:
Batches¶
GET /api/v1/batches¶
List batches with optional filters.
Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | sentStatus | string | Filter by sent status | | deviceId | string | Filter by device | | limit | int | Max results (default: 100) |
Response:
{
"success": true,
"data": {
"batches": [
{
"id": "batch-001",
"identifier": "B-2024-001234",
"name": "Batch 1234",
"status": "complete",
"bottleCount": 50,
"createdAt": "2024-01-01T10:00:00Z"
}
],
"total": 100
}
}
POST /api/v1/batches¶
Create new batch.
Request:
{
"batch": {
"name": "Batch 1234",
"deviceId": "device-001"
},
"bottles": ["bottle-001", "bottle-002", "bottle-003"]
}
Response:
{
"success": true,
"data": {
"batch": {
"id": "batch-001",
"identifier": "B-2024-001234",
"name": "Batch 1234",
"status": "in_progress"
},
"bottleCount": 3
}
}
GET /api/v1/batches/{id}¶
Get batch with bottles.
Response:
{
"success": true,
"data": {
"batch": {
"id": "batch-001",
"identifier": "B-2024-001234",
"name": "Batch 1234",
"status": "complete",
"batchSize": 50,
"createdAt": "2024-01-01T10:00:00Z",
"deviceId": "device-001"
},
"bottles": [
{
"id": "bottle-001",
"identifier": "010460000000001721ABC123KRIPTO1234"
}
]
}
}
POST /api/v1/batches/{id}/bottles¶
Add bottles to batch.
Request:
Response:
Scanner¶
GET /api/v1/scanner/status¶
Get scanner status.
Response:
{
"success": true,
"data": {
"connected": true,
"lastScan": "2024-01-01T10:00:00Z",
"scanCount": 1234
}
}
POST /api/v1/scanner/validate¶
Validate a code without storing.
Request:
Response:
{
"success": true,
"data": {
"valid": true,
"gtin": "04600000000001",
"duplicate": false,
"message": null
}
}
Invalid Response:
{
"success": true,
"data": {
"valid": false,
"gtin": null,
"duplicate": false,
"message": "Invalid code length for industry"
}
}
POST /api/v1/scanner/scan¶
Process scan (validate and store if valid).
Request:
Response:
{
"success": true,
"data": {
"valid": true,
"gtin": "04600000000001",
"duplicate": false,
"bottleId": "bottle-001"
}
}
GET /api/v1/scanner/recent¶
Get recent scan results.
Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | limit | int | Max results (default: 50) |
Response:
{
"success": true,
"data": [
{
"code": "010460000000001721ABC123KRIPTO1234",
"valid": true,
"timestamp": "2024-01-01T10:00:00Z"
}
]
}
Connection¶
GET /api/v1/connection/mode¶
Get current connection mode.
Response:
Mode Values: DIRECT_CLOUD, VIA_LOCAL_SERVER
PUT /api/v1/connection/mode¶
Set connection mode.
Request:
Response:
GET /api/v1/connection/status¶
Get connection status.
Response:
{
"success": true,
"data": {
"mode": "DIRECT_CLOUD",
"connected": true,
"lastSync": "2024-01-01T10:00:00Z",
"queueSize": 0
}
}
Cloud Operations¶
GET /api/v1/cloud/lines¶
Get production lines from cloud.
Response:
{
"success": true,
"data": [
{
"id": "line-001",
"name": "Milk Line A",
"identifier": "ML-A",
"companyId": "company-001",
"productId": "product-001",
"party": "PARTY-001"
}
]
}
GET /api/v1/cloud/codes¶
Get identification codes from cloud.
Response:
GET /api/v1/cloud/company¶
Get company information.
Response:
{
"success": true,
"data": {
"id": "company-001",
"name": "Demo Factory LLC",
"industry": "milk",
"timeZone": "Europe/Moscow"
}
}
POST /api/v1/cloud/sync¶
Trigger manual sync.
Response:
Sync Queue¶
GET /api/v1/sync/status¶
Get sync queue status.
Response:
{
"success": true,
"data": {
"queueSize": 123,
"oldestItem": "2024-01-01T09:00:00Z",
"lastSync": "2024-01-01T10:00:00Z",
"syncErrors": 0
}
}
Mock (Testing Only)¶
POST /api/v1/mock/scan¶
Simulate a barcode scan (mock mode only).
Request:
Response:
POST /api/v1/mock/generate-code¶
Generate a valid code for testing.
Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | industry | string | Industry type (default: milk) |
Response:
Factory Server API¶
Devices¶
GET /api/v1/devices¶
List all devices.
Response:
{
"success": true,
"data": [
{
"id": "edge-001",
"name": "Production Line 1",
"status": "online",
"lastHeartbeat": "2024-01-01T10:00:00Z",
"connectionMode": "VIA_LOCAL_SERVER"
}
]
}
POST /api/v1/devices¶
Register new device.
Request:
GET /api/v1/devices/{id}¶
Get device details.
PUT /api/v1/devices/{id}¶
Update device.
DELETE /api/v1/devices/{id}¶
Remove device.
POST /api/v1/devices/{id}/heartbeat¶
Record device heartbeat.
Request:
Sync¶
POST /api/v1/sync¶
Receive sync data from edge.
Request:
Connection Settings¶
GET /api/v1/connection/settings¶
Get global connection settings.
Response:
{
"success": true,
"data": {
"defaultMode": "VIA_LOCAL_SERVER",
"allowDeviceOverride": true,
"cloudSyncEnabled": true
}
}
PUT /api/v1/connection/settings¶
Update global connection settings.
Request:
Dashboard¶
GET /api/v1/dashboard/overview¶
Get dashboard statistics.
Response:
{
"success": true,
"data": {
"devicesOnline": 5,
"devicesOffline": 1,
"bottlesToday": 12345,
"batchesToday": 246,
"palletsTodayfalse": 12,
"syncQueueTotal": 0
}
}
WebSocket APIs¶
Edge Service WebSocket¶
Connect to ws://localhost:8080/ws (note: NOT /api/v1/ws)
Events:
// Scan event
{
"type": "scan",
"data": {
"code": "010460000000001721ABC123KRIPTO1234",
"valid": true,
"timestamp": "2024-01-01T10:00:00Z"
}
}
// Batch event
{
"type": "batch",
"data": {
"action": "created",
"batchId": "batch-001"
}
}
// Sync event
{
"type": "sync",
"data": {
"status": "completed",
"synced": 45
}
}
Factory Server WebSocket¶
Connect to ws://localhost:8081/ws/dashboard
Events:
// Device status update
{
"type": "device_status",
"data": {
"deviceId": "edge-001",
"status": "online"
}
}
// Production metrics
{
"type": "metrics",
"data": {
"bottlesPerMinute": 50,
"batchesCompleted": 5
}
}
Error Responses¶
All endpoints return errors in this format:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": {
"field": "identifier",
"reason": "Required field missing"
}
}
}
Common Error Codes:
| Code | HTTP Status | Description |
|---|---|---|
| UNAUTHORIZED | 401 | Invalid or missing token |
| FORBIDDEN | 403 | Insufficient permissions |
| NOT_FOUND | 404 | Resource not found |
| VALIDATION_ERROR | 400 | Invalid request |
| CONFLICT | 409 | Duplicate resource |
| SERVER_ERROR | 500 | Internal server error |
Server Management API (Factory Server)¶
All routes below are scoped to the factory server (http://localhost:8081) and
require a valid user JWT in Authorization: Bearer <token> (see
SERVER_MANAGEMENT.md for the authentication model).
Device commands¶
| Method | Path | Purpose |
|---|---|---|
| POST | /api/v1/devices/{id}/commands |
Dispatch a DeviceCommand and await its result |
| GET | /api/v1/devices/{id}/commands |
List dispatched commands for the device (paginated) |
| GET | /api/v1/devices/{id}/commands/{cmdId} |
Single command record |
| GET | /api/v1/devices/{id}/logs |
Device logs (streamed on demand or pushed via WS) |
| GET | /api/v1/devices/connected |
Live WebSocket sessions snapshot |
Request body for POST /commands:
Supported command.type values: restart_service, reboot_os, shutdown_os,
force_sync, clear_queue, pull_logs, push_config, update_firmware,
exec_shell, enable_device, disable_device.
Device groups¶
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/device-groups |
List groups |
| POST | /api/v1/device-groups |
Create |
| GET | /api/v1/device-groups/{id} |
Fetch one |
| PUT | /api/v1/device-groups/{id} |
Update |
| DELETE | /api/v1/device-groups/{id} |
Delete |
| GET | /api/v1/device-groups/{id}/members |
List members |
| POST | /api/v1/device-groups/{id}/members |
Add devices { "deviceIds": [...] } |
| DELETE | /api/v1/device-groups/{id}/members |
Remove devices |
| POST | /api/v1/device-groups/{id}/commands |
Bulk dispatch |
Firmware¶
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/firmware/releases |
List releases |
| POST | /api/v1/firmware/releases?version=...&filename=... |
Upload artifact (raw body) |
| GET | /api/v1/firmware/releases/{id} |
Metadata |
| DELETE | /api/v1/firmware/releases/{id} |
Remove release + artifact |
| GET | /api/v1/firmware/releases/{id}/artifact |
Binary download (used by devices) |
| GET | /api/v1/firmware/deployments?releaseId=...&deviceId=... |
Deployment history |
| POST | /api/v1/firmware/deployments |
Deploy { "releaseId": "...", "deviceIds": [...], "groupId": "..." } |
Device config (server-managed)¶
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/devices/{id}/config |
Current desired config |
| PUT | /api/v1/devices/{id}/config |
Update + optionally push to the device |
Audit + bootstrap¶
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/audit-log |
Filter by userId, entityId, since, limit |
| POST | /api/v1/devices/{id}/token |
Issue a device JWT (used by the edge service to open /ws/device) |
WebSocket endpoints¶
| Path | Auth | Purpose |
|---|---|---|
/ws/device?token=<deviceJwt> |
device JWT | Bidirectional command channel (edge → factory) |
/ws/dashboard?token=<userJwt> |
user JWT | Real-time event stream to the UI |
See SERVER_MANAGEMENT.md for the event schema and subscription envelope.
MARS L2 (WET/DRY) endpoints¶
Added in the L2 rollout (Phase 3-8). Mounted on the edge service
alongside the legacy API. Endpoints return HTTP 503 with body
{"error":{"code":"SERVICE_UNAVAILABLE"}} on installations where the
corresponding service hasn't been wired yet (progressive roll-out).
Scanners (multi-instance)¶
GET /api/v1/scanners— list configured scanners.PUT /api/v1/scanners— replace with up to 10 scanners.GET /api/v1/scanners/{index}/status— connection + role snapshot.POST /api/v1/scanners/{index}/test— force reconnect / health check.
Scanner config fields include transport (TCP/SERIAL/MODBUS/ETHERNET_IP/USB_HID),
model (COGNEX_DATAMAN/HIKROBOT_ID/DATALOGIC_MATRIX/DATALOGIC_HANDHELD/GENERIC)
and role (ADD/REMOVE/VERIFY_LOCAL_BUFFER/REJECTION/PACKAGE/BOX/REJECTION_FEEDBACK).
Printers (multi-instance)¶
GET /api/v1/printers— list.PUT /api/v1/printers— replace.GET /api/v1/printers/schema— per-model parameter schema.GET /api/v1/printers/{index}/status.POST /api/v1/printers/{index}/print— body{code, params}.POST /api/v1/printers/{index}/test.GET/POST/PUT/DELETE /api/v1/printers/{index}/templates[/{name}].
PLCs (multi-PLC)¶
GET /api/v1/plcs,PUT /api/v1/plcs— list / replace.GET /api/v1/plcs/{id}/health.POST /api/v1/plcs/{id}/test.GET /api/v1/plcs/{id}/tags— OPC-UA browse (or configured bindings for protocols without native browsing).GET /api/v1/plc/bindings,PUT /api/v1/plc/bindings.
Supported protocol values: MODBUS_TCP, MODBUS_RTU, OPC_UA,
PROFINET (via MOXA gateway), ETHERNET_IP, TCP_SOCKET.
UPS (WET cabinets)¶
GET /api/v1/ups/status.POST /api/v1/ups/test.POST /api/v1/ups/simulate— dev helper{onBattery, batteryPercent}.
GPIO (physical buttons + LEDs)¶
GET /api/v1/gpio/status.POST /api/v1/gpio/buttons/{name}/test.POST /api/v1/gpio/leds/{name}/set— body{on}.
Events / Journal (HMI-style)¶
GET /api/v1/events— filters:severity,category,source,from_us,to_us,ack,limit,cursor. Timestamps are microseconds since epoch.POST /api/v1/events— operator-created note.GET /api/v1/events/{id}.POST /api/v1/events/{id}/ack.POST /api/v1/events/ack-all.GET /api/v1/events/export?format=csv|json|xml— streams file download withContent-Disposition: attachment.
Batch accounting (Партионный учёт)¶
POST /api/v1/batch-accounting/buffer/load—{batchIdentifier}.GET /api/v1/batch-accounting/buffer— loaded/consumed/remaining.DELETE /api/v1/batch-accounting/buffer?batch=<id>.POST /api/v1/batch-accounting/verify—{code, consumedBy?}.
WebSocket¶
/wson the edge — existing channel, extended with message typeeventcarrying the new journal entries in real time.
Self-service catalogs (factory server)¶
Runtime-editable lists that replace the hard-coded enums. Reads are
public; writes require ADMIN / MANAGER.
GET/POST /api/v1/catalog/cloud-endpoints
DELETE /api/v1/catalog/cloud-endpoints/{id}
GET/POST /api/v1/catalog/sites
DELETE /api/v1/catalog/sites/{code}
GET/POST /api/v1/catalog/variants
DELETE /api/v1/catalog/variants/{code}
Self-service on edge (/api/v1/config + catalog)¶
GET /api/v1/catalog/scanners # driver metadata
GET /api/v1/catalog/printers
GET /api/v1/catalog/plc-protocols
GET /api/v1/config # current overrides (l2.override.*)
PUT /api/v1/config # { set: {key:value}, unset: [key], actor }
DELETE /api/v1/config # drop all overrides
POST /api/v1/config/reload # re-read DB, re-apply drivers in place
GET /api/v1/self-service/status # sanity endpoint for scripts
The scanners / printers / plcs overrides are JSON-encoded arrays
of the same shape the PUT /api/v1/scanners endpoint accepts. Calls to
that endpoint automatically write the override — so operator edits
survive a restart without touching edge-service.yaml.
See SELF_SERVICE.md for the playbooks (adding a factory, a cloud endpoint, a new cabinet variant, hot-swapping scanners on a live line).
Back to README | User Guide | Deployment Guide | Server Management | Rollout | Self-service