openapi: 3.1.0
info:
  title: Device Operations API
  version: '1.0'
  summary: Operator-facing API for reading device telemetry and sending device commands
  description: |-
    Direct access to device telemetry and commands - as a value added service.
    
    This API exposes access to individual devices as well as paginated bulk access, including telemetry.
    Commands can be sent, including custom commands.
  contact:
    name: Wunder Mobility GmbH
    url: 'https://wundermobility.com'
    email: info@wundermobility.com
servers:
  - url: 'https://{environment}.api.gourban.services/v1/{tenantId}/ops-api'
    description: Wunder Device Operations API
    variables:
      environment:
        default: go
        enum:
          - go
          - go-staging
      tenantId:
        default: demo
        description: Tenant identifier of your system. Usually 8 characters.
security:
  - bearerAuth: []
paths:
  '/devices':
    get:
      summary: List all devices for a tenant enriched with their latest telemetry
      operationId: getLastReceivedEvents
      description: |-
        Retrieves all devices for the tenant and enriches each one with its latest cached telemetry event
        if present. Devices without a cached event are returned with `event: null`.

        Results are paginated and sorted by device id for stable iteration across pages.
        Requires `DEVICE_READ` permission on at least one branch of the caller's tenant.
      parameters:
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            default: 0
          description: Zero-based page index
        - name: size
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 1000
            default: 100
          description: Page size (1 to 1000)
      responses:
        '200':
          $ref: '#/components/responses/OperatorDeviceTelemetryResponsePage'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: ../models/ErrorResponse.yaml
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: ../models/ErrorResponse.yaml
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: ../models/ErrorResponse.yaml
  '/devices/{deviceId}':
    parameters:
      - $ref: '#/components/parameters/deviceId'
    get:
      summary: Get latest received telemetry event for a device
      operationId: getLastReceivedEvent
      description: |-
        Returns the most recent cached telemetry event for the given device.
        Requires `DEVICE_READ` permission on at least one branch of the caller's tenant.
      responses:
        '200':
          $ref: '#/components/responses/OperatorDeviceEventResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: ../models/ErrorResponse.yaml
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: ../models/ErrorResponse.yaml
  '/devices/{deviceId}/execute-command':
    parameters:
      - $ref: '#/components/parameters/deviceId'
    post:
      summary: Execute a device command
      operationId: executeCommand
      description: |-
        Send a command to a device through its integration.
        Requires `DEVICE_SEND_COMMAND` permission on at least one branch of the caller's tenant.

        Integrations may not implement every command; unsupported commands result in a `200 OK` response
        whose body has `status=ERROR` and `errorCode=DI100`. Refer to the integration documentation for
        the supported command set per device type.
      requestBody:
        $ref: '#/components/requestBodies/CommandRequest'
      responses:
        '200':
          $ref: '#/components/responses/CommandResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: ../models/ErrorResponse.yaml
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: ../models/ErrorResponse.yaml
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: ../models/ErrorResponse.yaml
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT bearer token issued by the auth service
  parameters:
    deviceId:
      name: deviceId
      in: path
      required: true
      schema:
        type: string
      description: Device unique identifier within the tenant
  schemas:
    OperatorDeviceEvent:
      type: object
      description: Latest telemetry event for a device.
      properties:
        eventId:
          type: string
          format: uuid
          description: Unique event identifier
        timestamp:
          type: string
          format: date-time
          description: Event timestamp
        telemetry:
          $ref: '#/components/schemas/DeviceTelemetry'
        state:
          $ref: '#/components/schemas/DeviceState'
    DeviceTelemetry:
      type: object
      description: Vehicle and IoT device telemetry data. Fields populated depend on the device type.
      properties:
        coordinates:
          type: object
          description: Vehicle position
          properties:
            latitude:
              type: number
              format: float
            longitude:
              type: number
              format: float
        kilometers:
          type: number
          format: double
          description: Odometer reading
        speed:
          type: number
          format: float
          description: Vehicle speed
        helmetCount:
          type: integer
          description: Helmet holder reading
        rfidCardHolderCount:
          type: integer
          description: RFID card holder reading
        stateOfCharge:
          type: integer
          description: Vehicle battery state of charge (percent)
        remainingKilometers:
          type: number
          format: double
          description: Remaining kilometers reported by the device
        iotModuleBatteries:
          type: array
          description: IoT module battery readings
          items:
            type: object
            properties:
              voltage:
                type: number
                format: float
              stateOfCharge:
                type: integer
        vehicleBatteries:
          type: array
          description: Vehicle battery readings
          items:
            type: object
            properties:
              serialNumber:
                type: string
              slot:
                type: integer
              voltage:
                type: number
                format: float
              stateOfCharge:
                type: integer
              stateOfHealth:
                type: integer
              temperature:
                type: array
                description: Temperature readings (one entry per sensor).
                items:
                  type: integer
              chargeCycles:
                type: integer
        networkSignal:
          type: object
          description: Cellular network signal information
          properties:
            strengthDbm:
              type: integer
              description: Signal strength in dBm
            strengthCsq:
              type: integer
              description: Signal strength on the CSQ scale (0-31, 99 = unknown)
            type:
              type: string
              description: Network type (e.g. gsm, lte)
        versionInfo:
          type: object
          description: Firmware/hardware version metadata
          properties:
            iotFirmware:
              type: string
            iotHardware:
              type: string
            ecuFirmware:
              type: string
            ecuHardware:
              type: string
            modemFirmware:
              type: string
            modemHardware:
              type: string
            bluetoothFirmware:
              type: string
            versionUpdatedAt:
              type: string
              format: date-time
            updateStatus:
              type: integer
            updateStatusUpdatedAt:
              type: string
              format: date-time
            iccid:
              type: string
            iccidUpdatedAt:
              type: string
              format: date-time
            iotMode:
              type: string
              enum:
                - IOT_MODE_UNSPECIFIED
                - IOT_MODE_UNKNOWN
                - IOT_MODE_TEST
                - IOT_MODE_NORMAL
                - IOT_MODE_SHIPPING
                - IOT_MODE_TRANSPORT
            iotModeUpdatedAt:
              type: string
              format: date-time
    DeviceState:
      type: object
      description: Device state snapshot. Fields populated depend on the device type.
      properties:
        powerState:
          type: string
          enum:
            - POWER_STATE_UNSPECIFIED
            - POWER_STATE_UNKNOWN
            - POWER_STATE_ON
            - POWER_STATE_OFF
        chargingState:
          type: string
          enum:
            - CHARGING_STATE_UNSPECIFIED
            - CHARGING_STATE_UNKNOWN
            - CHARGING_STATE_UNPLUGGED
            - CHARGING_STATE_PLUGGED
            - CHARGING_STATE_CHARGING
        tailboxLidState:
          type: string
          enum:
            - TAILBOX_LID_STATE_UNSPECIFIED
            - TAILBOX_LID_STATE_UNSET
            - TAILBOX_LID_STATE_OPEN
            - TAILBOX_LID_STATE_CLOSED
        helmetState:
          type: string
          enum:
            - HELMET_STATE_UNSPECIFIED
            - HELMET_STATE_UNKNOWN
            - HELMET_STATE_MISSING
            - HELMET_STATE_PARTIALLY
            - HELMET_STATE_PRESENT
        lockState:
          type: string
          enum:
            - LOCK_STATE_UNSPECIFIED
            - LOCK_STATE_UNKNOWN
            - LOCK_STATE_LOCKED
            - LOCK_STATE_UNLOCKED
            - LOCK_STATE_LOCKING
            - LOCK_STATE_UNLOCKING
            - LOCK_STATE_TAMPERED
            - LOCK_STATE_INVALID
        immobilizerState:
          type: string
          enum:
            - IMMOBILIZER_STATE_UNSPECIFIED
            - IMMOBILIZER_STATE_UNKNOWN
            - IMMOBILIZER_STATE_LOCKED
            - IMMOBILIZER_STATE_UNLOCKED
        doorState:
          type: array
          description: |-
            Door states. Always contains a general state (`DOOR_STATE_OPEN` or `DOOR_STATE_CLOSED`)
            when available, followed by per-door entries (e.g. `DOOR_STATE_FRONT_LEFT_OPEN`).
          items:
            type: string
            enum:
              - DOOR_STATE_UNSPECIFIED
              - DOOR_STATE_UNKNOWN
              - DOOR_STATE_CLOSED
              - DOOR_STATE_OPEN
              - DOOR_STATE_FRONT_LEFT_OPEN
              - DOOR_STATE_FRONT_RIGHT_OPEN
              - DOOR_STATE_REAR_LEFT_OPEN
              - DOOR_STATE_REAR_RIGHT_OPEN
              - DOOR_STATE_HOOD_OPEN
              - DOOR_STATE_TRUNK_OPEN
              - DOOR_STATE_ALL_DOORS_OPEN
              - DOOR_STATE_ALL_DOORS_CLOSED
        keyInIgnitionState:
          type: string
          enum:
            - KEY_IN_IGNITION_STATE_UNSPECIFIED
            - KEY_IN_IGNITION_STATE_UNKNOWN
            - KEY_IN_IGNITION_STATE_PRESENT
            - KEY_IN_IGNITION_STATE_MISSING
        keyInHolderState:
          type: string
          enum:
            - KEY_IN_HOLDER_STATE_UNSPECIFIED
            - KEY_IN_HOLDER_STATE_UNKNOWN
            - KEY_IN_HOLDER_STATE_PRESENT
            - KEY_IN_HOLDER_STATE_MISSING
        parkBrakeState:
          type: string
          enum:
            - PARK_BRAKE_STATE_UNSPECIFIED
            - PARK_BRAKE_STATE_UNKNOWN
            - PARK_BRAKE_STATE_ON
            - PARK_BRAKE_STATE_OFF
        batteryCoverState:
          type: string
          enum:
            - BATTERY_COVER_STATE_UNSPECIFIED
            - BATTERY_COVER_STATE_UNKNOWN
            - BATTERY_COVER_STATE_OPEN
            - BATTERY_COVER_STATE_CLOSED
            - BATTERY_COVER_STATE_INVALID
        batteryLockState:
          type: string
          enum:
            - BATTERY_LOCK_STATE_UNSPECIFIED
            - BATTERY_LOCK_STATE_UNKNOWN
            - BATTERY_LOCK_STATE_LOCKED
            - BATTERY_LOCK_STATE_UNLOCKED
            - BATTERY_LOCK_STATE_TAMPERED
            - BATTERY_LOCK_STATE_INVALID
        saddleState:
          type: string
          enum:
            - SADDLE_STATE_UNSPECIFIED
            - SADDLE_STATE_UNKNOWN
            - SADDLE_STATE_OPEN
            - SADDLE_STATE_CLOSED
        docking:
          type: object
          description: Vehicle docking status
          properties:
            serialNumber:
              type: string
            status:
              type: string
              enum:
                - DOCKING_STATUS_UNSPECIFIED
                - DOCKING_STATUS_UNKNOWN
                - DOCKING_STATUS_CONNECTED
                - DOCKING_STATUS_DISCONNECTED
        onlineState:
          type: string
          enum:
            - ONLINE_STATE_UNSPECIFIED
            - ONLINE_STATE_UNKNOWN
            - ONLINE_STATE_ON
            - ONLINE_STATE_OFF
  requestBodies:
    CommandRequest:
      description: Command request
      content:
        application/json:
          schema:
            type: object
            required:
              - command
            properties:
              command:
                type: string
                enum:
                  - START
                  - STOP
                  - LOCK
                  - UNLOCK
                  - OPEN_SADDLE
                  - OPEN_TAIL_BOX
                  - LOCK_TAILBOX
                  - LOCATE
                  - ALARM_ON
                  - ALARM_OFF
                  - SET_MAX_SPEED
                  - IS_ONLINE
                  - REBOOT
                  - UNLOCK_BIKE_LOCK
                  - SET_AVAILABILITY_INDICATOR
                  - CUSTOM_COMMAND
                  - LIGHTS_ON
                  - LIGHTS_OFF
                  - SET_MODE_TEST
                  - SET_MODE_NORMAL
                  - SET_MODE_SHIPPING
                  - SET_MODE_TRANSPORT
                  - RETRIEVE_ICCID
                  - RETRIEVE_VERSION
                  - RETRIEVE_CONFIG
                  - MOBILISE
                  - IMMOBILISE
              parameters:
                type: object
                description: Command-specific parameters
                additionalProperties:
                  type: string
              async:
                type: boolean
                description: If true, dispatch fire-and-forget and return immediately
              timeout:
                type: integer
                description: Timeout in milliseconds for synchronous commands
          examples:
            Lock:
              value:
                command: LOCK
            SetMaxSpeed:
              value:
                command: SET_MAX_SPEED
                parameters:
                  maxSpeed: '25'
            CustomCommandOkai:
              summary: Send an integration-specific raw command, example OKAI
              value:
                command: CUSTOM_COMMAND
                parameters:
                  customCommand: 'AT+GTRTO=password,C,,0,,,,,,FFFF$'
  responses:
    OperatorDeviceEventResponse:
      description: Latest telemetry event for the device
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/OperatorDeviceEvent'
          examples:
            OkaiEb100:
              summary: OKAI EB100 scooter parked, locked, battery at 95%
              value:
                eventId: 3b8e1b9e-0e7e-4f4f-9f1e-1e1e1e1e1e1e
                timestamp: '2026-05-21T10:05:30Z'
                telemetry:
                  coordinates:
                    latitude: 53.54256
                    longitude: 10.001005
                  kilometers: 234.7
                  speed: 0
                  stateOfCharge: 95
                  iotModuleBatteries:
                    - voltage: 4.175
                      stateOfCharge: 100
                  vehicleBatteries:
                    - voltage: 53.108
                      stateOfCharge: 95
                  networkSignal:
                    strengthDbm: -91
                    strengthCsq: 11
                    type: gsm
                state:
                  powerState: POWER_STATE_OFF
                  chargingState: CHARGING_STATE_UNPLUGGED
                  lockState: LOCK_STATE_LOCKED
                  batteryLockState: BATTERY_LOCK_STATE_LOCKED
                  onlineState: ONLINE_STATE_ON
    OperatorDeviceTelemetryResponsePage:
      description: Paginated list of devices with their latest telemetry event
      content:
        application/json:
          schema:
            type: object
            properties:
              content:
                type: array
                maxItems: 1000
                items:
                  type: object
                  properties:
                    deviceId:
                      type: string
                    event:
                      nullable: true
                      description: Latest telemetry event for the device, or null if none has been received
                      allOf:
                        - $ref: '#/components/schemas/OperatorDeviceEvent'
              page:
                type: object
                properties:
                  size:
                    type: integer
                  number:
                    type: integer
                  totalElements:
                    type: integer
                    format: int64
                  totalPages:
                    type: integer
          examples:
            OkaiEb100Fleet:
              summary: Two OKAI EB100 scooters, one with telemetry, one yet to report
              value:
                content:
                  - deviceId: '867035040740050'
                    event:
                      eventId: 3b8e1b9e-0e7e-4f4f-9f1e-1e1e1e1e1e1e
                      timestamp: '2026-05-21T10:05:30Z'
                      telemetry:
                        coordinates:
                          latitude: 53.54256
                          longitude: 10.001005
                        kilometers: 234.7
                        speed: 0
                        stateOfCharge: 95
                        iotModuleBatteries:
                          - voltage: 4.175
                            stateOfCharge: 100
                        vehicleBatteries:
                          - voltage: 53.108
                            stateOfCharge: 95
                        networkSignal:
                          strengthDbm: -91
                          strengthCsq: 11
                          type: gsm
                      state:
                        powerState: POWER_STATE_OFF
                        chargingState: CHARGING_STATE_UNPLUGGED
                        lockState: LOCK_STATE_LOCKED
                        batteryLockState: BATTERY_LOCK_STATE_LOCKED
                        onlineState: ONLINE_STATE_ON
                  - deviceId: '867035040740051'
                    event: null
                page:
                  size: 100
                  number: 0
                  totalElements: 2
                  totalPages: 1
    CommandResponse:
      description: Command response
      content:
        application/json:
          schema:
            type: object
            properties:
              status:
                type: string
                enum:
                  - OK
                  - ERROR
                  - UNKNOWN
                  - TIMED_OUT
              timestamp:
                type: string
                format: date-time
              errorCode:
                type: string
              errorMessage:
                type: string
              customCommandResponse:
                type: object
                description: Integration-specific response payload
                additionalProperties: true
          examples:
            Success:
              value:
                status: OK
                timestamp: '2026-05-21T10:30:00Z'
            UnsupportedCommand:
              value:
                status: ERROR
                errorCode: DI100
                errorMessage: The device does not support this command
                timestamp: '2026-05-21T10:30:00Z'
