{
  "openapi": "3.0.0",
  "paths": {
    "/status/configuration": {
      "get": {
        "description": "Returns the full system configuration including version, uptime, IP addresses, support mode status, device count, forge status, and device model.",
        "operationId": "StatusController_getConfiguration",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Full system configuration",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "success": true,
                    "version": "0.4.161",
                    "uptime": 86400,
                    "ipAddress": "192.168.1.100",
                    "supportModeEnabled": false,
                    "deviceCount": 3,
                    "forgeStatus": "running",
                    "deviceModel": "21port-S19"
                  }
                }
              }
            }
          }
        },
        "summary": "Get system configuration",
        "tags": [
          "Status",
          "Status"
        ]
      }
    },
    "/status": {
      "get": {
        "description": "Returns the operational status of the API service. Use this endpoint to verify that the service is running and accepting requests.",
        "operationId": "StatusController_status",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Service is operational and ready to handle requests",
            "schema": {
              "example": {
                "operational": true
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatusResponse"
                }
              }
            }
          }
        },
        "summary": "Health Check",
        "tags": [
          "Status",
          "Status"
        ]
      }
    },
    "/status/version": {
      "get": {
        "description": "Returns the current version of the API service.",
        "operationId": "StatusController_version",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Current version of the API service",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "0.1.0"
                }
              }
            }
          }
        },
        "summary": "Get API Version",
        "tags": [
          "Status",
          "Status"
        ]
      }
    },
    "/status/summary": {
      "get": {
        "description": "Returns a full summary of the system. The exact shape depends on the application — heater summary on 21control, port summary on 21port.",
        "operationId": "StatusController_summary",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Full system summary"
          }
        },
        "summary": "Get system summary",
        "tags": [
          "Status",
          "Status"
        ]
      }
    },
    "/status/system": {
      "get": {
        "description": "Returns comprehensive system status including device model, pairing status, product identifier, and firmware version.",
        "operationId": "StatusController_system",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Complete system status information including device details and configuration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SystemInfoResponse"
                }
              }
            }
          }
        },
        "summary": "Get System Information",
        "tags": [
          "Status",
          "Status"
        ]
      }
    },
    "/status/diagnostic": {
      "get": {
        "description": "Executes comprehensive system diagnostic checks including network connectivity, service status, miner health, and other app-specific signals.",
        "operationId": "StatusController_diagnostic",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Diagnostic results covering network, services, and app-specific health checks"
          }
        },
        "summary": "Run system diagnostics",
        "tags": [
          "Status",
          "Status"
        ]
      }
    },
    "/status/download/logs": {
      "get": {
        "description": "Streams a ZIP archive containing system log files for debugging and support.",
        "operationId": "StatusController_downloadLogs",
        "parameters": [],
        "responses": {
          "200": {
            "description": "ZIP file containing system logs",
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "500": {
            "description": "Failed to generate log archive"
          }
        },
        "summary": "Download system logs as ZIP",
        "tags": [
          "Status",
          "Status"
        ]
      }
    },
    "/status/documentation": {
      "get": {
        "description": "Returns the Swagger/OpenAPI documentation JSON for this API.",
        "operationId": "StatusController_documentation",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OpenAPI specification document"
          }
        },
        "summary": "Get API documentation",
        "tags": [
          "Status",
          "Status"
        ]
      }
    },
    "/enrollment": {
      "get": {
        "description": "Returns the same enrollment object carried on GET /status/system. Read hasCert as the durable truth and state as the in-flight truth.",
        "operationId": "EnrollmentController_state",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Current enrollment state"
          }
        },
        "summary": "Get enrollment state",
        "tags": [
          "enrollment"
        ]
      }
    },
    "/enrollment/reset": {
      "post": {
        "description": "Returns the enrollment state machine to idle so the device accepts invitations again, for a device stuck in failed or wedged mid-flow. Clears the state machine and the pending keypair only — a persisted certificate is never discarded, so calling this on an enrolled device does not un-enrol it.",
        "operationId": "EnrollmentController_reset",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The state after the reset, always idle"
          }
        },
        "summary": "Reset enrollment state",
        "tags": [
          "enrollment"
        ]
      }
    },
    "/bluetooth/dropBonding/{id}": {
      "get": {
        "description": "Removes the bluetooth bonding between the device service and 21energy app",
        "operationId": "BluetoothController_dropBonding",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "The key of the bonding to be removed",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "summary": "",
        "tags": [
          "bluetooth"
        ]
      }
    },
    "/mining/enable": {
      "post": {
        "description": "Enables or disables mining on a specific miner or all miners. If minerId is provided, only that miner is affected. Otherwise, the operation applies to all registered miners.",
        "operationId": "MiningController_setMinerEnabled",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "Miner enable/disable configuration",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetMinerStatusDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operation result",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "success": true
                  }
                }
              }
            }
          }
        },
        "summary": "Enable or disable miners",
        "tags": [
          "Mining"
        ]
      }
    },
    "/mining/powerLevel": {
      "post": {
        "description": "Sets the power level for a specific miner or all mining devices. If minerId is provided, only that miner is affected. Otherwise, the operation applies to all devices managed by 21forge.",
        "operationId": "MiningController_setPowerLevel",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "Power level configuration",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetPowerLevelDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operation result",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "success": true
                  }
                }
              }
            }
          }
        },
        "summary": "Set mining power level",
        "tags": [
          "Mining"
        ]
      }
    },
    "/mining/poolConfig": {
      "get": {
        "description": "Returns the current mining pool configuration from 21forge.",
        "operationId": "MiningController_getPoolConfig",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Current pool configuration",
            "content": {
              "application/json": {
                "schema": {
                  "example": [
                    {
                      "url": "stratum+tcp://pool.example.com:3333",
                      "user": "miner1.worker"
                    },
                    {
                      "url": "stratum+tcp://backup.example.com:4444",
                      "user": "miner1.worker-backup"
                    }
                  ]
                }
              }
            }
          }
        },
        "summary": "Get pool configuration",
        "tags": [
          "Mining"
        ]
      },
      "post": {
        "description": "Updates the port-wide global mining pool configuration. If a configuration is set, miners will receive this config when being plugged in. If null is provided, the global config is cleared and connected miners' pool configurations will not be overwritten.",
        "operationId": "MiningController_setPoolConfig",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "Pool configuration to apply, or null to clear the global config",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetPoolConfigDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operation result",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "success": true
                  }
                }
              }
            }
          }
        },
        "summary": "Set global pool configuration",
        "tags": [
          "Mining"
        ]
      }
    },
    "/mining/miner-details/{minerId}": {
      "get": {
        "description": "Returns detailed information for a specific miner, including status, hashboard data, pool connections, tuner status, and errors. Not all miners support retrieving detailed data (e.g. Antminer S9). Fields like hashboards, tunerStatus, and errors may be empty for unsupported models.",
        "operationId": "MiningController_getMinerDetails",
        "parameters": [
          {
            "name": "minerId",
            "required": true,
            "in": "path",
            "description": "Forge device ID of the miner",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Miner details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MinerDetailsResponseDto"
                }
              }
            }
          },
          "409": {
            "description": "DEVICE_LOCKED — the miner refuses us until credentials are supplied"
          }
        },
        "summary": "Get miner details",
        "tags": [
          "Mining"
        ]
      }
    },
    "/mining/lockState": {
      "get": {
        "description": "Which miners 21forge holds no working credentials for. Served from the cached watch snapshot, so it answers even while a miner refuses us.",
        "operationId": "MinerCredentialsController_getLockState",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "devices": [
                      {
                        "id": "AntminerBraiinsGrpc_10.8.10.2",
                        "locked": true,
                        "lockReason": "credentialsRejected"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "summary": "Lock state of every miner",
        "tags": [
          "Mining"
        ]
      }
    },
    "/mining/credentials/{minerId}": {
      "post": {
        "description": "Hands 21forge the credentials for a miner it holds no working password for. On acceptance the miner is configured, watched, and no longer locked. A rejected password is not stored — the miner stays locked and its lock reason becomes credentialsRejected.",
        "operationId": "MinerCredentialsController_setCredentials",
        "parameters": [
          {
            "name": "minerId",
            "required": true,
            "in": "path",
            "description": "Forge device ID of the miner",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Credentials to hand to the miner",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetMinerCredentialsDto"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted; the miner is configured and no longer locked"
          },
          "400": {
            "description": "Unknown device, or malformed body"
          },
          "409": {
            "description": "UNLOCK_IN_PROGRESS — a submission for this miner is already being validated"
          },
          "422": {
            "description": "CREDENTIALS_REJECTED — the miner refused these credentials"
          },
          "501": {
            "description": "FORGE_UNSUPPORTED — the installed 21forge predates this endpoint"
          },
          "502": {
            "description": "MINER_UNREACHABLE — the miner is offline; its lock state is untouched"
          }
        },
        "summary": "Supply credentials for a locked miner",
        "tags": [
          "Mining"
        ]
      }
    },
    "/schedule": {
      "get": {
        "description": "Returns all currently defined mining schedules (legacy v1 and v2 interval schedules, tagged by recurrence).",
        "operationId": "ScheduleController_getSchedules",
        "parameters": [],
        "responses": {
          "200": {
            "description": "List of all mining schedules"
          }
        },
        "summary": "Get all schedules",
        "tags": [
          "Schedule"
        ]
      },
      "post": {
        "description": "Creates a new mining schedule. Supports legacy (v1) and v2 interval schedules (daily/weekly/monthly/oneTime).",
        "operationId": "ScheduleController_createSchedule",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "Schedule configuration",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateScheduleDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Schedule created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "command": "setSchedule",
                    "success": true
                  }
                }
              }
            }
          },
          "409": {
            "description": "Schedule collides with one or more existing schedules"
          }
        },
        "summary": "Create a new schedule",
        "tags": [
          "Schedule"
        ]
      }
    },
    "/schedule/{id}": {
      "put": {
        "description": "Updates one or more fields on an existing mining schedule identified by its ID.",
        "operationId": "ScheduleController_updateSchedule",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "The unique ID of the schedule to update",
            "schema": {
              "example": "98e6a7a0-bd32-4797-b440-9014249bf7a0",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Fields to update on the schedule",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateScheduleDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Schedule updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "command": "updateSchedule",
                    "success": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input data or unknown ID"
          },
          "409": {
            "description": "Schedule collides with one or more existing schedules"
          }
        },
        "summary": "Update an existing schedule",
        "tags": [
          "Schedule"
        ]
      },
      "delete": {
        "description": "Deletes an existing mining schedule by its ID and cancels any associated cron jobs.",
        "operationId": "ScheduleController_deleteSchedule",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "The unique ID of the schedule to delete",
            "schema": {
              "example": "98e6a7a0-bd32-4797-b440-9014249bf7a0",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Schedule deleted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "command": "deleteSchedule",
                    "id": "98e6a7a0-bd32-4797-b440-9014249bf7a0",
                    "success": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing schedule ID"
          }
        },
        "summary": "Delete a schedule",
        "tags": [
          "Schedule"
        ]
      }
    },
    "/system/timezone": {
      "get": {
        "description": "Returns the currently configured timezone as an IANA timezone identifier.",
        "operationId": "SystemController_getTimezone",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Current timezone",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "timezone": "Europe/Berlin"
                  }
                }
              }
            }
          }
        },
        "summary": "Get device timezone",
        "tags": [
          "System"
        ]
      },
      "post": {
        "description": "Updates the device timezone. Expects a valid IANA timezone identifier.",
        "operationId": "SystemController_setTimezone",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "Timezone configuration",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetTimezoneDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operation result",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "success": true
                  }
                }
              }
            }
          }
        },
        "summary": "Set device timezone",
        "tags": [
          "System"
        ]
      }
    },
    "/system/alias": {
      "get": {
        "description": "Returns the custom name assigned to this device. Returns null if no name has been set.",
        "operationId": "SystemController_getRoom",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Current alias name",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "room": "Server Room"
                  }
                }
              }
            }
          }
        },
        "summary": "Get device alias name",
        "tags": [
          "System"
        ]
      },
      "post": {
        "description": "Sets a custom name for this device.",
        "operationId": "SystemController_setRoom",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "alias name configuration",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetAliasDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operation result",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "success": true
                  }
                }
              }
            }
          }
        },
        "summary": "Set device alias name",
        "tags": [
          "System"
        ]
      }
    },
    "/system/registerProduct": {
      "post": {
        "description": "Stores the order number for product registration.",
        "operationId": "SystemController_registerProduct",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "Product registration data",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterProductDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operation result",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "success": true
                  }
                }
              }
            }
          }
        },
        "summary": "Register product",
        "tags": [
          "System"
        ]
      }
    },
    "/system/support": {
      "post": {
        "description": "Starts or stops the support VPN connection for remote assistance.",
        "operationId": "SystemController_setSupport",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "Support VPN configuration",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetSupportDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operation result",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "success": true
                  }
                }
              }
            }
          }
        },
        "summary": "Enable or disable support VPN",
        "tags": [
          "System"
        ]
      }
    },
    "/update/available": {
      "get": {
        "description": "Returns all packages that have an available update.",
        "operationId": "UpdateController_getAvailable",
        "parameters": [],
        "responses": {
          "200": {
            "description": "List of packages with available updates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/UpdateAvailableDto"
                  }
                }
              }
            }
          }
        },
        "summary": "List available updates",
        "tags": [
          "Update"
        ]
      }
    },
    "/update/trigger": {
      "post": {
        "description": "Triggers an update for all configured software packages.",
        "operationId": "UpdateController_triggerUpdate",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Operation result",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "success": true
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Trigger software update",
        "tags": [
          "Update"
        ]
      }
    },
    "/update/auto": {
      "get": {
        "description": "Returns whether automatic software updates are currently enabled.",
        "operationId": "UpdateController_getAutoUpdate",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Auto-update enabled status",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "software": false
                  }
                }
              }
            }
          }
        },
        "summary": "Get auto-update status",
        "tags": [
          "Update"
        ]
      },
      "put": {
        "description": "Enable or disable automatic software updates.",
        "operationId": "UpdateController_setAutoUpdate",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "Auto-update configuration",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetAutoUpdateDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operation result",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "success": true
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Set auto-update status",
        "tags": [
          "Update"
        ]
      }
    },
    "/history": {
      "get": {
        "description": "Returns time-series buckets for every miner that currently has recorded history. Each bucket represents a 5-minute point-in-time sample.",
        "operationId": "HistoryController_getAll",
        "parameters": [
          {
            "name": "since",
            "required": false,
            "in": "query",
            "description": "ISO 8601 timestamp with timezone. Only buckets at or after this time are returned.",
            "schema": {
              "example": "2026-04-13T11:00:00+02:00",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Historic metrics per series"
          }
        },
        "summary": "Get historic metrics for all miners",
        "tags": [
          "History"
        ]
      }
    },
    "/history/miners": {
      "get": {
        "description": "Returns the list of miner ids that currently have recorded history.",
        "operationId": "HistoryController_getSeries",
        "parameters": [],
        "responses": {
          "200": {
            "description": "List of series ids"
          }
        },
        "summary": "List miner series ids",
        "tags": [
          "History"
        ]
      }
    },
    "/history/{seriesId}": {
      "get": {
        "description": "Returns the 5-minute time-series buckets for a specific miner id.",
        "operationId": "HistoryController_getOne",
        "parameters": [
          {
            "name": "seriesId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "required": false,
            "in": "query",
            "description": "ISO 8601 timestamp with timezone. Only buckets at or after this time are returned.",
            "schema": {
              "example": "2026-04-13T11:00:00+02:00",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Historic metrics for the requested miner"
          }
        },
        "summary": "Get historic metrics for a single miner",
        "tags": [
          "History"
        ]
      }
    },
    "/mining-device/dashboard-access/{deviceId}": {
      "post": {
        "description": "Generates a time-limited LAN-only URL of the form http://miner-<token>.local/ that proxies the miner's native dashboard through the device PI.",
        "operationId": "DashboardAccessController_issue",
        "parameters": [
          {
            "name": "deviceId",
            "required": true,
            "in": "path",
            "description": "Forge device ID of the target miner.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IssueDashboardAccessDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DashboardAccessResponseDto"
                }
              }
            }
          }
        },
        "summary": "Issue a dashboard access URL for a miner",
        "tags": [
          "Mining"
        ]
      }
    },
    "/mining-device/dashboard-access/{token}": {
      "delete": {
        "operationId": "DashboardAccessController_revoke",
        "parameters": [
          {
            "name": "token",
            "required": true,
            "in": "path",
            "description": "32-char lowercase-hex token.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": ""
          }
        },
        "summary": "Revoke a dashboard access token",
        "tags": [
          "Mining"
        ]
      },
      "get": {
        "operationId": "DashboardAccessController_getOne",
        "parameters": [
          {
            "name": "token",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DashboardAccessResponseDto"
                }
              }
            }
          }
        },
        "summary": "Get details of a single dashboard access token",
        "tags": [
          "Mining"
        ]
      }
    },
    "/mining-device/dashboard-access": {
      "get": {
        "operationId": "DashboardAccessController_list",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DashboardAccessResponseDto"
                  }
                }
              }
            }
          }
        },
        "summary": "List active dashboard access tokens",
        "tags": [
          "Mining"
        ]
      }
    }
  },
  "info": {
    "title": "21port API",
    "description": "The REST api of a 21energy port",
    "version": "0.5.110",
    "contact": {
      "name": "21energy GmbH",
      "url": "https://21energy.com",
      "email": "office@21energy.com"
    }
  },
  "tags": [],
  "servers": [
    {
      "url": "http://192.168.178.157/21port/"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearer": {
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "type": "http"
      }
    },
    "schemas": {
      "StatusResponse": {
        "type": "object",
        "properties": {
          "operational": {
            "type": "boolean",
            "description": "Indicates whether the API service is operational and ready to accept requests",
            "example": true
          }
        },
        "required": [
          "operational"
        ]
      },
      "HashboardInfo": {
        "type": "object",
        "properties": {
          "actualChipCount": {
            "type": "number",
            "description": "Chips the board actually reports, or null when not reported",
            "nullable": true,
            "example": 76
          },
          "model": {
            "type": "string",
            "description": "Board model as reported by the miner, or null",
            "nullable": true,
            "example": "BM1366"
          },
          "serialNumber": {
            "type": "string",
            "description": "Board serial number, or null. Useful as line traceability",
            "nullable": true,
            "example": "HB-0001-A"
          },
          "voltage": {
            "type": "number",
            "description": "Board voltage as reported by the miner, or null. Note this drops to near zero on EVERY board when the miner stops, so it reports whether the miner is running rather than the health of one board.",
            "nullable": true,
            "example": 15.45
          },
          "chipTempC": {
            "type": "number",
            "description": "Chip temperature in degrees C as reported by the miner, or null",
            "nullable": true,
            "example": 62.5
          },
          "boardTempC": {
            "type": "number",
            "description": "Board temperature in degrees C as reported by the miner, or null",
            "nullable": true,
            "example": 48
          },
          "pcbInletC": {
            "type": "number",
            "description": "PCB inlet temperature in degrees C as reported by the miner, or null",
            "nullable": true,
            "example": 31.2
          },
          "pcbOutletC": {
            "type": "number",
            "description": "PCB outlet temperature in degrees C as reported by the miner, or null",
            "nullable": true,
            "example": 44.7
          }
        },
        "required": [
          "actualChipCount",
          "model",
          "serialNumber",
          "voltage",
          "chipTempC",
          "boardTempC",
          "pcbInletC",
          "pcbOutletC"
        ]
      },
      "HashboardSummary": {
        "type": "object",
        "properties": {
          "count": {
            "type": "number",
            "description": "Number of hashboards physically connected",
            "example": 1
          },
          "boards": {
            "description": "One entry per connected hashboard",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HashboardInfo"
            }
          }
        },
        "required": [
          "count",
          "boards"
        ]
      },
      "SystemInfoResponse": {
        "type": "object",
        "properties": {
          "model": {
            "type": "string",
            "description": "The product model of this device",
            "example": "Ofen 2"
          },
          "isPaired": {
            "type": "boolean",
            "description": "Signifies whether this device is currently paired to a phone",
            "example": true
          },
          "productId": {
            "type": "string",
            "description": "A device-unique name, built as `<model> <last 8 hex digits of the eth0 MAC>`. Despite the name it identifies this individual unit, not the product type: use `model` to determine which product this is and never parse this field for that. Retained under this name for backwards compatibility with existing clients.",
            "example": "Ofen 2 42TH f92c70"
          },
          "version": {
            "type": "string",
            "description": "The current software version of this device. Deprecated in favour of `controlVersion`, which carries the same value under the agreed name; this key stays for existing clients.",
            "example": "0.4.106",
            "deprecated": true
          },
          "controlVersion": {
            "type": "string",
            "description": "The 21control/21port application version running on this device - the same value as the deprecated `version` key, under the name agreed with 21production. Composed by the base controller from the application version, so it cannot drift between applications.",
            "example": "0.5.93"
          },
          "usbDevices": {
            "type": "array",
            "description": "USB devices currently attached, read from the USB descriptor via sysfs — nothing is mounted. Always present: an empty array means nothing is attached, while an attached device that supplied no serial appears as an entry with serial null. Hubs are excluded.",
            "example": [
              {
                "port": "1-1.3",
                "serial": "0501a1b2c3d4e5f6",
                "vendorId": "0781",
                "usbProductId": "5583",
                "manufacturer": "SanDisk",
                "product": "Ultra Fit",
                "isMassStorage": true
              }
            ]
          },
          "enrollment": {
            "type": "object",
            "description": "Enrollment status. Read hasCert as the durable truth and state as the in-flight truth: state starts at idle on every boot, so an enrolled device reports state idle with hasCert true.",
            "example": {
              "state": "idle",
              "hasCert": true,
              "cn": "CN-0001",
              "notAfter": "2027-08-14T09:14:22.318Z",
              "lastError": null,
              "lastAttemptAt": null,
              "retryAfter": null
            }
          },
          "operationalMode": {
            "type": "string",
            "description": "21control only - absent on other applications. The device operational mode. 21production assert this is NORMAL before running a heater, so it is carried here to keep their pre-test check to a single read of this route.",
            "example": "NORMAL"
          },
          "temperatureControlEnabled": {
            "type": "boolean",
            "description": "21control only - absent on other applications. Whether thermostat mode is regulating temperature. This is the second pre-test assertion 21production make, and it requires false.",
            "example": false
          },
          "hashboards": {
            "description": "21control only - absent on other applications. Connected hashboards, or null when the miner service could not be reached in time. Null means unknown and must never be read as zero boards; retry rather than treating it as a variant mismatch.",
            "nullable": true,
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/HashboardSummary"
              }
            ]
          }
        },
        "required": [
          "model",
          "isPaired",
          "productId",
          "version",
          "controlVersion",
          "usbDevices",
          "enrollment"
        ]
      },
      "SetMinerStatusDto": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Enable (true) or disable (false) the miner",
            "example": true
          },
          "minerId": {
            "type": "string",
            "description": "Target a specific miner by ID. If omitted, applies to all miners.",
            "example": "miner-001"
          }
        },
        "required": [
          "enabled"
        ]
      },
      "SetPowerLevelDto": {
        "type": "object",
        "properties": {
          "level": {
            "type": "number",
            "description": "The power level to set for mining devices",
            "example": 3
          },
          "minerId": {
            "type": "string",
            "description": "Target a specific miner by ID. If omitted, applies to all miners.",
            "example": "miner-001"
          }
        },
        "required": [
          "level"
        ]
      },
      "PoolConfigEntryDto": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "The pool URL",
            "example": "stratum+tcp://pool.example.com:3333"
          },
          "user": {
            "type": "string",
            "description": "The pool username/worker",
            "example": "miner1.worker"
          }
        },
        "required": [
          "url",
          "user"
        ]
      },
      "SetPoolConfigDto": {
        "type": "object",
        "properties": {
          "configurations": {
            "nullable": true,
            "description": "Array of pool configurations to apply as the port-wide global config. Pass null to clear the global config so newly plugged-in miners keep their own pool configuration.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PoolConfigEntryDto"
            }
          }
        },
        "required": [
          "configurations"
        ]
      },
      "MinerDetailsResponseDto": {
        "type": "object",
        "properties": {
          "hashboardStatus": {
            "description": "Hashboard statuses",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "errors": {
            "description": "Active miner errors",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "minerId": {
            "type": "string",
            "description": "Forge device ID of the miner"
          }
        },
        "required": [
          "hashboardStatus",
          "errors",
          "minerId"
        ]
      },
      "SetMinerCredentialsDto": {
        "type": "object",
        "properties": {
          "user": {
            "type": "string",
            "description": "Account to authenticate as. Omit to keep the account the device already uses — the common case, where only the password changed. Send it to switch accounts (e.g. a Whatsminer v3 super-password change needs \"super\").",
            "example": "root"
          },
          "password": {
            "type": "string",
            "description": "Password for the mining device. May be empty, but must be present.",
            "example": "NEW_PASSWORD"
          }
        },
        "required": [
          "password"
        ]
      },
      "DevicePowerLevelDto": {
        "type": "object",
        "properties": {
          "deviceId": {
            "type": "string",
            "description": "The device ID to target",
            "example": "miner-1"
          },
          "level": {
            "type": "number",
            "description": "Power level to set for this device",
            "example": 3
          }
        },
        "required": [
          "deviceId",
          "level"
        ]
      },
      "CreateScheduleDto": {
        "type": "object",
        "properties": {
          "recurrence": {
            "type": "string",
            "enum": [
              "oneTime",
              "daily",
              "weekly",
              "monthly"
            ],
            "description": "Recurrence of the schedule. Omit for a legacy (v1) weekday-bitmask schedule."
          },
          "repeat": {
            "type": "boolean",
            "description": "Legacy only: whether the schedule repeats weekly."
          },
          "days": {
            "type": "number",
            "description": "Legacy only: weekday bitmask (bit 0-6 = Sun-Sat).",
            "example": 62
          },
          "startTime": {
            "type": "string",
            "description": "Start time in HH:mm (legacy, daily, weekly, monthly).",
            "example": "08:00"
          },
          "endTime": {
            "type": "string",
            "description": "End time in HH:mm (legacy, daily, weekly, monthly).",
            "example": "17:00"
          },
          "startWeekday": {
            "type": "number",
            "description": "Weekly only: start weekday (Sun=0 .. Sat=6).",
            "example": 1
          },
          "endWeekday": {
            "type": "number",
            "description": "Weekly only: end weekday (Sun=0 .. Sat=6).",
            "example": 5
          },
          "startDayOfMonth": {
            "type": "number",
            "description": "Monthly only: start day-of-month (1-31, clamped to month length).",
            "example": 1
          },
          "endDayOfMonth": {
            "type": "number",
            "description": "Monthly only: end day-of-month (1-31, clamped to month length).",
            "example": 3
          },
          "startIso": {
            "type": "string",
            "description": "One-time only: absolute ISO-8601 start instant.",
            "example": "2026-01-15T20:00:00+01:00"
          },
          "endIso": {
            "type": "string",
            "description": "One-time only: absolute ISO-8601 end instant.",
            "example": "2026-01-16T06:00:00+01:00"
          },
          "deviceLevels": {
            "description": "Per-device power level configurations",
            "example": [
              {
                "deviceId": "miner-1",
                "level": 3
              },
              {
                "deviceId": "miner-2",
                "level": 5
              }
            ],
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DevicePowerLevelDto"
            }
          },
          "active": {
            "type": "boolean",
            "default": true,
            "description": "Whether the schedule is active"
          }
        },
        "required": [
          "deviceLevels",
          "active"
        ]
      },
      "UpdateScheduleDto": {
        "type": "object",
        "properties": {
          "recurrence": {
            "type": "string",
            "enum": [
              "oneTime",
              "daily",
              "weekly",
              "monthly"
            ],
            "description": "Recurrence (v2 schedules)."
          },
          "repeat": {
            "type": "boolean",
            "description": "Legacy only: whether the schedule repeats weekly."
          },
          "days": {
            "type": "number",
            "description": "Legacy only: weekday bitmask (bit 0-6 = Sun-Sat).",
            "example": 62
          },
          "startTime": {
            "type": "string",
            "description": "Start time in HH:mm.",
            "example": "08:00"
          },
          "endTime": {
            "type": "string",
            "description": "End time in HH:mm.",
            "example": "17:00"
          },
          "startWeekday": {
            "type": "number",
            "description": "Weekly only: start weekday (Sun=0 .. Sat=6)."
          },
          "endWeekday": {
            "type": "number",
            "description": "Weekly only: end weekday (Sun=0 .. Sat=6)."
          },
          "startDayOfMonth": {
            "type": "number",
            "description": "Monthly only: start day-of-month (1-31)."
          },
          "endDayOfMonth": {
            "type": "number",
            "description": "Monthly only: end day-of-month (1-31)."
          },
          "startIso": {
            "type": "string",
            "description": "One-time only: absolute ISO-8601 start instant."
          },
          "endIso": {
            "type": "string",
            "description": "One-time only: absolute ISO-8601 end instant."
          },
          "deviceLevels": {
            "description": "Per-device power level configurations",
            "example": [
              {
                "deviceId": "miner-1",
                "level": 3
              }
            ],
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DevicePowerLevelDto"
            }
          },
          "active": {
            "type": "boolean",
            "description": "Whether the schedule is active"
          }
        }
      },
      "SetTimezoneDto": {
        "type": "object",
        "properties": {
          "timezone": {
            "type": "string",
            "description": "IANA timezone identifier",
            "example": "Europe/Berlin"
          }
        },
        "required": [
          "timezone"
        ]
      },
      "SetAliasDto": {
        "type": "object",
        "properties": {
          "alias": {
            "type": "string",
            "description": "The custom name for this device/alias",
            "example": "Server Room"
          }
        },
        "required": [
          "alias"
        ]
      },
      "RegisterProductDto": {
        "type": "object",
        "properties": {
          "orderNumber": {
            "type": "string",
            "description": "The order number to register this product",
            "example": "ORD-2025-001"
          }
        },
        "required": [
          "orderNumber"
        ]
      },
      "SetSupportDto": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Enable (true) or disable (false) the support VPN",
            "example": true
          }
        },
        "required": [
          "enabled"
        ]
      },
      "UpdateAvailableDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Package/software identifier",
            "example": "controller_x"
          },
          "displayName": {
            "type": "string",
            "description": "Human-readable name",
            "example": "Ofen Control Software"
          },
          "currentVersion": {
            "type": "string",
            "description": "Currently installed version",
            "example": "2.1.0"
          },
          "availableVersion": {
            "type": "string",
            "description": "Available update version",
            "example": "2.2.0"
          },
          "category": {
            "type": "string",
            "enum": [
              "software",
              "miner-firmware"
            ],
            "description": "Update category",
            "example": "software"
          },
          "track": {
            "type": "string",
            "enum": [
              "production",
              "beta",
              "development"
            ],
            "description": "apt repository track the update is coming from",
            "example": "production"
          }
        },
        "required": [
          "name",
          "displayName",
          "currentVersion",
          "availableVersion",
          "category",
          "track"
        ]
      },
      "SetAutoUpdateDto": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Enable (true) or disable (false) automatic updates",
            "example": true
          }
        },
        "required": [
          "enabled"
        ]
      },
      "IssueDashboardAccessDto": {
        "type": "object",
        "properties": {
          "ttlSeconds": {
            "type": "number",
            "description": "Lifetime of the access token in seconds. Clamped to PROXY_TOKEN_MAX_TTL_SECONDS on the server.",
            "example": 43200
          },
          "label": {
            "type": "string",
            "description": "Optional human-readable label (e.g. miner alias) stored with the token for listing.",
            "example": "Antminer S19 #3"
          }
        }
      },
      "DashboardAccessResponseDto": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "description": "32-char lowercase-hex access token (also the DNS label).",
            "example": "0123456789abcdef0123456789abcdef"
          },
          "deviceId": {
            "type": "string",
            "description": "Forge device ID the token grants access to.",
            "example": "dev-1"
          },
          "label": {
            "type": "string",
            "description": "Human-readable label.",
            "example": "Antminer S19 #3"
          },
          "url": {
            "type": "string",
            "description": "LAN URL the customer opens in a browser on the same WiFi.",
            "example": "http://miner-0123456789abcdef0123456789abcdef.local/"
          },
          "expiresAt": {
            "type": "number",
            "description": "Unix ms when the token expires.",
            "example": 1700000000000
          },
          "expiresInSeconds": {
            "type": "number",
            "description": "Seconds until expiry.",
            "example": 43200
          },
          "issuedAt": {
            "type": "number",
            "description": "Unix ms when the token was issued.",
            "example": 1699950000000
          }
        },
        "required": [
          "token",
          "deviceId",
          "label",
          "url",
          "expiresAt",
          "expiresInSeconds",
          "issuedAt"
        ]
      }
    }
  }
}