Skip to content

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:

  1. User Token - Obtained from /sign_in, used for device selection
  2. Device Token - Obtained from /devices/{id}/activate, used for operations

Edge Service API

Health Check

GET /health

Check service health.

curl http://localhost:8080/health

Response:

{
  "status": "healthy",
  "version": "1.0.0",
  "uptime": 3600
}


Authentication

POST /api/v1/auth/login

Authenticate user with local credentials.

Request:

{
  "username": "operator",
  "password": "operator123"
}

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:

{
  "email": "user@company.com",
  "password": "password123"
}

Response:

{
  "success": true,
  "status": "SIGNED_IN",
  "message": "Successfully signed in"
}

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:

{
  "success": true,
  "message": "Device deprovisioned"
}

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:

curl "http://localhost:8080/api/v1/bottles?status=0&limit=50"

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:

{
  "success": true,
  "data": {
    "total": 1234,
    "byStatus": {
      "0": 500,
      "1": 700,
      "2": 30,
      "3": 4
    }
  }
}

PATCH /api/v1/bottles/{id}/status

Update bottle status.

Request:

{
  "status": 1
}

Response:

{
  "success": true
}


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:

["bottle-004", "bottle-005", "bottle-006"]

Response:

{
  "success": true,
  "data": {
    "added": 3
  }
}


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:

{
  "code": "010460000000001721ABC123KRIPTO1234"
}

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:

{
  "code": "010460000000001721ABC123KRIPTO1234",
  "timestamp": "2024-01-01T10:00:00Z"
}

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:

{
  "success": true,
  "data": {
    "mode": "DIRECT_CLOUD",
    "allowSwitch": true
  }
}

Mode Values: DIRECT_CLOUD, VIA_LOCAL_SERVER

PUT /api/v1/connection/mode

Set connection mode.

Request:

{
  "mode": "VIA_LOCAL_SERVER"
}

Response:

{
  "success": true,
  "data": {
    "mode": "VIA_LOCAL_SERVER",
    "message": "Mode changed successfully"
  }
}

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:

{
  "success": true,
  "data": {
    "codes": [...],
    "remaining": 10000
  }
}

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:

{
  "success": true,
  "data": {
    "synced": 45,
    "failed": 0,
    "remaining": 0
  }
}


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:

{
  "code": "010460000000001721ABC123KRIPTO1234"
}

Response:

{
  "success": true
}

POST /api/v1/mock/generate-code

Generate a valid code for testing.

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | industry | string | Industry type (default: milk) |

Response:

{
  "success": true,
  "data": {
    "code": "010460000000001721XYZ789KRIPTO5678"
  }
}


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:

{
  "identifier": "edge-001",
  "name": "Production Line 1",
  "productionLineId": "line-001"
}

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:

{
  "timestamp": "2024-01-01T10:00:00Z",
  "queueSize": 0,
  "status": "healthy"
}


Sync

POST /api/v1/sync

Receive sync data from edge.

Request:

{
  "deviceId": "edge-001",
  "bottles": [...],
  "batches": [...],
  "pallets": [...]
}


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:

{
  "defaultMode": "VIA_LOCAL_SERVER",
  "allowDeviceOverride": true
}


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:

{
  "command": { "type": "force_sync", "id": "cmd-uuid" },
  "timeoutMs": 15000
}

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 with Content-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

  • /ws on the edge — existing channel, extended with message type event carrying 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