{
  "openapi": "3.0.0",
  "paths": {
    "/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"
        ]
      }
    },
    "/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"
        ]
      }
    },
    "/heater/miner/lock": {
      "get": {
        "description": "Whether 21forge holds working credentials for the miner. A locked miner is reachable and healthy — it just refuses us — so it is never removed and waits indefinitely for a password.",
        "operationId": "HeaterController_getMinerLock",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "locked": true,
                    "lockReason": "credentialsRejected"
                  }
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/scheduledHeating/{id}": {
      "delete": {
        "description": "Delete a scheduled heating option by ID",
        "operationId": "HeaterController_deleteSchedule",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "delete a scheduler by ID",
            "schema": {
              "example": "98e6a7a0-bd32-4797-b440-9014249bf7a0",
              "allOf": [
                {
                  "$ref": "#/components/schemas/ScheduledHeatingDeleteDto"
                }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Whether the operation succeeded. `false` means the command failed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "boolean",
                  "example": true
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      },
      "put": {
        "description": "Update a scheduled heating option",
        "operationId": "HeaterController_updateSchedule",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "The unique ID of the scheduled heating option to update.",
            "schema": {
              "example": "98e6a7a0-bd32-4797-b440-9014249bf7a0",
              "allOf": [
                {
                  "$ref": "#/components/schemas/PathUpdateScheduledHeatingDto"
                }
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Fields to update on the schedule",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BodyUpdateScheduledHeatingDto"
              },
              "examples": {
                "default": {
                  "summary": "Update schedule fields",
                  "value": {
                    "active": true,
                    "startTime": "08:00",
                    "endTime": "17:00",
                    "heatingLevel": 3,
                    "days": 62
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated scheduled heating entry\n\nScheduled heating option updated successfully."
          },
          "400": {
            "description": "Invalid input data or unknown ID"
          },
          "404": {
            "description": "Scheduled heating option with the given ID not found."
          },
          "500": {
            "description": "An unexpected error occurred while updating the schedule."
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/enable": {
      "post": {
        "description": "Turns the heater on or off.",
        "operationId": "HeaterController_enable",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "Enable or disable the heater",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HeaterEnabledDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Whether the operation succeeded. `false` means the command failed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "boolean",
                  "example": true
                }
              }
            }
          },
          "409": {
            "description": "DEVICE_LOCKED — the miner refuses us until credentials are supplied"
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/enableSupport": {
      "get": {
        "description": "Enable the heater device support mode",
        "operationId": "HeaterController_enableSupportViaGet",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Whether the operation succeeded. `false` means the command failed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "boolean",
                  "example": true
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/scheduledHeating": {
      "get": {
        "description": "Returns an array of all currently defined heating schedules.",
        "operationId": "HeaterController_getAllSchedules",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Successfully retrieved all scheduled heating options.",
            "schema": {
              "example": [
                {
                  "id": "98e6a7a0-bd32-4797-b440-9014249bf7a0",
                  "repeat": true,
                  "days": 62,
                  "startTime": "08:00",
                  "endTime": "10:30",
                  "heatingLevel": 3,
                  "active": true
                },
                {
                  "id": "0e6bdd3b-0814-4dc6-8524-baf7bd14d217",
                  "repeat": false,
                  "days": 127,
                  "startTime": "18:00",
                  "endTime": "20:00",
                  "heatingLevel": 2,
                  "active": false
                }
              ]
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ScheduledHeatingResponseDto"
                  }
                }
              }
            }
          },
          "204": {
            "description": "No schedules found (empty array returned).",
            "content": {
              "application/json": {
                "schema": {
                  "example": []
                }
              }
            }
          },
          "500": {
            "description": "An error occurred while retrieving schedules."
          }
        },
        "summary": "Retrieve all scheduled heating options",
        "tags": [
          "Heater"
        ]
      },
      "post": {
        "description": "Add a scheuled heating action",
        "operationId": "HeaterController_setScheduledHeating",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "add a scheduled heating action",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScheduledHeatingDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Whether the operation succeeded. `false` means the command failed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "boolean",
                  "example": true
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/fan/mode": {
      "get": {
        "deprecated": true,
        "description": "Gets the current fan mode. Deprecated for Ofen 2 (Does nothing).",
        "operationId": "HeaterController_getFanMode",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The current fan mode. Can be \"auto\" or \"manual\".",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "auto"
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/status/fan": {
      "get": {
        "deprecated": true,
        "description": "Query the current fan speed. Deprecated for Ofen 2 (Does nothing).",
        "operationId": "HeaterController_getFanSpeed",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "number"
                }
              }
            },
            "description": ""
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/networkStatus": {
      "get": {
        "description": "DEPRECATED - please use /network/networkStatus instead - get the current connection state",
        "operationId": "HeaterController_getNetWorkConnectionStatus",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Network status summary",
            "schema": {
              "example": {
                "interface": "wlan0",
                "essid": "MyHomeWiFi",
                "minQuality": 40,
                "maxQuality": 72,
                "signalLevel": "-55 dBm"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NetworkStatusSummaryDto"
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/poolConfig": {
      "get": {
        "description": "Get the pool config for this device",
        "operationId": "HeaterController_getPoolConfig",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The updated pool configuration.\n\nBraiins Pool Config",
            "schema": {
              "example": {
                "poolUrl1": "stratum+tcp://pool1.example.com:3333",
                "poolUrl2": "stratum+tcp://backup-pool.example.com:4444",
                "username1": "miner1.worker",
                "username2": "miner1.worker-backup"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PoolConfigDto"
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      },
      "post": {
        "description": "Sets the pool config for this device",
        "operationId": "HeaterController_setPoolConfig",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "Braiins Pool Config",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PoolConfigDto"
              }
            }
          }
        },
        "responses": {
          "409": {
            "description": "DEVICE_LOCKED — the miner refuses us until credentials are supplied"
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/powerTarget": {
      "get": {
        "description": "Gets the current power target level. 0 is lowest level, 4 is highest level.",
        "operationId": "HeaterController_getPowerTarget",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "text/plain": {
                "schema": {
                  "type": "number",
                  "example": "3",
                  "minimum": 0,
                  "maximum": 4
                }
              }
            },
            "description": ""
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/powerTarget/lease": {
      "get": {
        "description": "Reports the power-target lease. Always answers, including on a device that has never held one, so a client can assert on it before starting work: a live lease means something else is already driving this device.",
        "operationId": "HeaterController_getPowerTargetLease",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Current lease state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "active": true,
                  "level": 4,
                  "expiresAt": "2026-08-14T09:02:00.000Z",
                  "remainingSeconds": 87
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/powerTarget/watt": {
      "get": {
        "description": "Gets the current power target in watts",
        "operationId": "HeaterController_getPowerTargetString",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "text/plain": {
                "schema": {
                  "type": "number",
                  "example": "675"
                }
              }
            },
            "description": ""
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/room": {
      "get": {
        "description": "Get given room name",
        "operationId": "HeaterController_getRoomName",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "example": "Living Room"
                }
              }
            },
            "description": ""
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      },
      "post": {
        "description": "Set custom ofen name",
        "operationId": "HeaterController_setRoomName",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "The to be given name of the room",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RoomNameDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Whether the operation succeeded. `false` means the command failed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "boolean",
                  "example": true
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/status/summary": {
      "get": {
        "description": "Query the current summary of the current heater operational values",
        "operationId": "HeaterController_getSummary",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Forge watch combined system status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "miningDevices": {
                    "count": 3,
                    "active": 2,
                    "faulty": 1,
                    "enabled": 2,
                    "hashRate": 13500,
                    "powerTargetW": 3000,
                    "powerConsumptionW": 2850,
                    "maxChipTemperature": 78,
                    "minChipTemperature": 54,
                    "lastSummaries": [
                      {
                        "bestShare": 120000,
                        "dynamicPerformanceScaling": {
                          "status": "ok",
                          "statusMessage": "running optimally"
                        },
                        "foundBlocks": 0,
                        "poolStatus": "Alive",
                        "power": {
                          "approxConsumptionW": 2850,
                          "efficiencyWMhs": 0.21,
                          "efficiencyJth": 28,
                          "limitW": 3000
                        },
                        "realHashrate": {
                          "mhs15M": 13500,
                          "mhs15S": 13480,
                          "mhs1H": 13300,
                          "mhs1M": 13510,
                          "mhs24H": 13000,
                          "mhs30M": 13440,
                          "mhs30S": 13470,
                          "mhs5M": 13520,
                          "mhs5S": 13490,
                          "mhsAv": 13200,
                          "mhsSinceRestart": 13150
                        },
                        "tunerStatus": "stable",
                        "temperature": {
                          "name": "chip0",
                          "degreesC": 72
                        }
                      }
                    ]
                  },
                  "smartMeters": {
                    "count": 1,
                    "active": 1
                  },
                  "forge": {
                    "requestedPowerConsumptionW": 3000,
                    "currentPowerConsumptionW": 2850,
                    "powerLevel": 2,
                    "status": "running"
                  },
                  "unixTimestamp": 1735741800
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/status/temperature": {
      "get": {
        "description": "Query the current temperature of the heater components",
        "operationId": "HeaterController_getTemperature",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "text/plain": {
                "schema": {
                  "type": "number",
                  "example": "75"
                }
              }
            },
            "description": ""
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/timezone": {
      "get": {
        "description": "Get the device's timezone",
        "operationId": "HeaterController_getTimeZone",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The device's timezone as the IANA tz identifier",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "example": "Europe/Vienna"
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/fan/mode/{mode}": {
      "post": {
        "deprecated": true,
        "description": "Sets the fan mode to \"auto\" (default) or \"manual\". If you set to \"manual\" you can adjust the fan speed using the \"fan/speed\" endpoint. \n Please BE AWARE that if the heater reaches HOT or DANGEROUS temperature levels, the fan speed is overwritten regardless if mode is \"auto\" or \"manual\". This is dangerous and will damage your heater if you are not careful. \n Deprecated for Ofen 2 (Does nothing)",
        "operationId": "HeaterController_setFanMode",
        "parameters": [
          {
            "name": "mode",
            "required": true,
            "in": "path",
            "description": "Value can either be \"auto\" or \"manual\"",
            "schema": {
              "example": "auto",
              "allOf": [
                {
                  "$ref": "#/components/schemas/FanModeDto"
                }
              ]
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Whether the operation succeeded. `false` means the command failed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "boolean",
                  "example": true
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/fan/{speed}": {
      "post": {
        "deprecated": true,
        "description": "Manually overwrites the fan speed. If the mode is set to \"auto\" (default) the speed will be adjusted automatically. If you want to set the fan speed permanent, please set the mode to \"manual\" first. Deprecated for Ofen 2 (Does nothing)",
        "operationId": "HeaterController_setFanSpeed",
        "parameters": [
          {
            "name": "speed",
            "required": true,
            "in": "path",
            "description": "Values between 0 (off) and 100 (full-speed)",
            "schema": {
              "$ref": "#/components/schemas/FanSpeedDto"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/miner/credentials": {
      "post": {
        "description": "Supplies 21forge with the credentials for a locked miner. On acceptance the miner is configured, watched, and no longer locked. A rejected password is not stored — the miner stays locked and its reason becomes credentialsRejected.",
        "operationId": "HeaterController_setMinerCredentials",
        "parameters": [],
        "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": "Malformed body — the password field is required, though it may be empty"
          },
          "409": {
            "description": "UNLOCK_IN_PROGRESS — a submission 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": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/powerTarget/{index}": {
      "post": {
        "description": "Switch to other power target level. Index must be a value between 0 and 4.",
        "operationId": "HeaterController_setPowerTarget",
        "parameters": [
          {
            "name": "index",
            "required": true,
            "in": "path",
            "description": "Values between 0 and 4 ",
            "schema": {
              "minimum": 0,
              "maximum": 4,
              "example": 2,
              "allOf": [
                {
                  "$ref": "#/components/schemas/AutotuningProfileDto"
                }
              ]
            }
          },
          {
            "name": "notifyApp",
            "required": false,
            "in": "query",
            "description": "Whether the app should be notified in real time about this change",
            "schema": {
              "default": true,
              "type": "boolean"
            }
          },
          {
            "name": "leaseSeconds",
            "required": false,
            "in": "query",
            "description": "Optional dead-man's switch on this power target. While a lease is live any repeat call refreshes it, and when the refreshes stop the device drops the power target to 0 on its own. Pass 0 to cancel a live lease explicitly. Omitting it sets the power target and leaves any live lease armed against the new level — it does not cancel one.",
            "schema": {
              "minimum": 10,
              "maximum": 900,
              "example": 120,
              "type": "number"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Whether the operation succeeded. `false` means the command failed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "boolean",
                  "example": true
                }
              }
            }
          },
          "409": {
            "description": "DEVICE_LOCKED — the miner refuses us until credentials are supplied"
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/heater/support": {
      "post": {
        "description": "Enables or disables the support mechanism on the heater.",
        "operationId": "HeaterController_setSupportEnabled",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "Enable or disable the support",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SupportDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Whether the operation succeeded. `false` means the command failed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "boolean",
                  "example": true
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "Heater"
        ]
      }
    },
    "/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"
        ]
      }
    },
    "/thermometer/set": {
      "post": {
        "description": "Registers a thermometer",
        "operationId": "ThermometerController_addThermometer",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddThermometerDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Whether the operation succeeded. `false` means the command failed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "boolean",
                  "example": true
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "thermometer"
        ]
      }
    },
    "/thermometer/delete": {
      "delete": {
        "description": "deletes the current thermometer",
        "operationId": "ThermometerController_removeThermometer",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Whether the operation succeeded. `false` means the command failed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "boolean",
                  "example": true
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "thermometer"
        ]
      }
    },
    "/thermometer/scan": {
      "get": {
        "description": "Scan for ble thermometer",
        "operationId": "ThermometerController_scan",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The BLE thermometers discovered by the scan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "thermometer"
        ]
      }
    },
    "/thermometer/data": {
      "get": {
        "description": "Retrieve the current data from the thermometer",
        "operationId": "ThermometerController_getData",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Summary of thermometer status and latest data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connectionError": {
                      "type": "boolean",
                      "description": "Whether there is a connection error with the thermometer"
                    },
                    "latestData": {
                      "type": "object",
                      "description": "Latest data received from the thermometer",
                      "additionalProperties": true,
                      "nullable": true
                    },
                    "thermometerPaired": {
                      "type": "boolean",
                      "description": "Whether a thermometer is paired"
                    }
                  },
                  "required": [
                    "connectionError",
                    "latestData",
                    "thermometerPaired"
                  ]
                },
                "example": {
                  "connectionError": false,
                  "latestData": {
                    "temperature": 22.4,
                    "humidity": 45,
                    "timestamp": "2026-01-27T10:15:30.000Z"
                  },
                  "thermometerPaired": true
                }
              }
            }
          }
        },
        "summary": "",
        "tags": [
          "thermometer"
        ]
      }
    },
    "/update/available": {
      "get": {
        "description": "Returns all packages (software and miner firmware) 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 the specified category.",
        "operationId": "UpdateController_triggerUpdate",
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "Update category to trigger",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TriggerUpdateDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operation result",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "success": true
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Trigger update",
        "tags": [
          "Update"
        ]
      }
    },
    "/update/auto": {
      "get": {
        "description": "Returns whether automatic updates are currently enabled for each category.",
        "operationId": "UpdateController_getAutoUpdate",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Auto-update enabled status per category",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "software": false,
                    "minerFirmware": false
                  }
                }
              }
            }
          }
        },
        "summary": "Get auto-update status",
        "tags": [
          "Update"
        ]
      },
      "put": {
        "description": "Enable or disable automatic updates for a specific category.",
        "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
                  }
                }
              }
            }
          }
        },
        "summary": "Set auto-update status",
        "tags": [
          "Update"
        ]
      }
    },
    "/history": {
      "get": {
        "description": "Returns the last 24 h of 5-minute point-in-time samples for this heater.",
        "operationId": "HistoryController_getHistory",
        "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 for this heater"
          }
        },
        "summary": "Get historic heater metrics",
        "tags": [
          "History"
        ]
      }
    }
  },
  "info": {
    "title": "21Energy Control API",
    "description": "The REST api of an 21energy Heater",
    "version": "0.5.110",
    "contact": {
      "name": "21energy GmbH",
      "url": "https://21energy.com",
      "email": "office@21energy.com"
    }
  },
  "tags": [],
  "servers": [
    {
      "url": "http://192.168.178.157/21control/"
    }
  ],
  "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"
        ]
      },
      "ScheduledHeatingDeleteDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID of the scheduled heating that should be deleted",
            "example": "98e6a7a0-bd32-4797-b440-9014249bf7a0"
          }
        },
        "required": [
          "id"
        ]
      },
      "HeaterEnabledDto": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Bosminer on (true) or off (false)",
            "example": true
          },
          "notifyApp": {
            "type": "boolean",
            "default": true,
            "description": "Whether the app should be notified in real time about this change",
            "example": true
          }
        },
        "required": [
          "enabled"
        ]
      },
      "ScheduledHeatingResponseDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "98e6a7a0-bd32-4797-b440-9014249bf7a0"
          },
          "recurrence": {
            "type": "string",
            "example": "legacy",
            "description": "Recurrence: legacy | daily | weekly | monthly | oneTime"
          },
          "repeat": {
            "type": "boolean",
            "example": true
          },
          "days": {
            "type": "number",
            "example": 62,
            "description": "Legacy bitmask for days (e.g., 62 = Mon–Fri)"
          },
          "startTime": {
            "type": "string",
            "example": "08:00"
          },
          "endTime": {
            "type": "string",
            "example": "10:30"
          },
          "startWeekday": {
            "type": "number",
            "example": 1,
            "description": "Weekly: start weekday (Sun=0 .. Sat=6)"
          },
          "endWeekday": {
            "type": "number",
            "example": 5,
            "description": "Weekly: end weekday (Sun=0 .. Sat=6)"
          },
          "startDayOfMonth": {
            "type": "number",
            "example": 1,
            "description": "Monthly: start day-of-month"
          },
          "endDayOfMonth": {
            "type": "number",
            "example": 3,
            "description": "Monthly: end day-of-month"
          },
          "startIso": {
            "type": "string",
            "example": "2026-01-15T20:00:00+01:00",
            "description": "One-time: ISO start"
          },
          "endIso": {
            "type": "string",
            "example": "2026-01-16T06:00:00+01:00",
            "description": "One-time: ISO end"
          },
          "heatingLevel": {
            "type": "number",
            "example": 3,
            "description": "Heating level between 0–4"
          },
          "active": {
            "type": "boolean",
            "example": true
          }
        },
        "required": [
          "id",
          "recurrence",
          "heatingLevel",
          "active"
        ]
      },
      "NetworkStatusSummaryDto": {
        "type": "object",
        "properties": {
          "interface": {
            "type": "string",
            "description": "Network interface name",
            "example": "wlan0"
          },
          "localIp": {
            "type": "string",
            "description": "Local IP address of the active network interface",
            "example": "192.168.1.42"
          },
          "essid": {
            "type": "string",
            "description": "Wireless network ESSID (if connected)",
            "example": "MyHomeWiFi"
          },
          "quality": {
            "type": "number",
            "description": "observed link quality",
            "example": 40
          },
          "maxQuality": {
            "type": "number",
            "description": "Maximum possible link quality",
            "example": 70
          },
          "signalLevel": {
            "type": "string",
            "description": "Current signal level",
            "example": "-55 dBm"
          }
        },
        "required": [
          "interface"
        ]
      },
      "PoolConfigDto": {
        "type": "object",
        "properties": {
          "poolUrl1": {
            "type": "string",
            "description": "The pool url of the primary mining pool",
            "example": "stratum+tcp://pool1.example.com:3333"
          },
          "poolUrl2": {
            "type": "string",
            "description": "The pool url of the backup mining pool. Optional.",
            "example": "stratum+tcp://backup-pool.example.com:4444"
          },
          "username1": {
            "type": "string",
            "description": "The username of the primary mining pool",
            "example": "miner1.worker"
          },
          "username2": {
            "type": "string",
            "description": "The username of the backup mining pool. Optional.",
            "example": "miner1.worker-backup"
          }
        },
        "required": [
          "poolUrl1",
          "username1"
        ]
      },
      "FanModeDto": {
        "type": "object",
        "properties": {
          "mode": {
            "type": "string",
            "description": "The fan operation mode: \"auto\" or \"manual\"",
            "example": "auto"
          }
        },
        "required": [
          "mode"
        ]
      },
      "FanSpeedDto": {
        "type": "object",
        "properties": {
          "speed": {
            "type": "number",
            "description": "The speed of the fan: 0 (off) - 100 (full speed)"
          }
        },
        "required": [
          "speed"
        ]
      },
      "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.",
            "example": "root"
          },
          "password": {
            "type": "string",
            "description": "Password for the mining device. May be empty, but must be present.",
            "example": "NEW_PASSWORD"
          }
        },
        "required": [
          "password"
        ]
      },
      "AutotuningProfileDto": {
        "type": "object",
        "properties": {
          "index": {
            "type": "number",
            "description": "The level for the new power target. Must be between 0 and 4, inclusive!",
            "example": 2,
            "minimum": 0,
            "maximum": 4
          }
        },
        "required": [
          "index"
        ]
      },
      "RoomNameDto": {
        "type": "object",
        "properties": {
          "room": {
            "type": "string",
            "description": "The custom name of the ofen",
            "example": "Living Room"
          }
        },
        "required": [
          "room"
        ]
      },
      "ScheduledHeatingDto": {
        "type": "object",
        "properties": {
          "recurrence": {
            "type": "string",
            "enum": [
              "oneTime",
              "daily",
              "weekly",
              "monthly"
            ],
            "description": "Recurrence of the schedule. Omit for a legacy (v1) weekday-bitmask schedule."
          },
          "active": {
            "type": "boolean",
            "default": true,
            "description": "informs if the schedule is active"
          },
          "heatingLevel": {
            "type": "number",
            "description": "level the ofen should apply in the schedule"
          },
          "days": {
            "type": "number",
            "description": "Legacy only: weekday bitmask (bit 0-6 = Sun-Sat). E.g. 127 means every day."
          },
          "repeat": {
            "type": "boolean",
            "default": false,
            "description": "Legacy only: whether the schedule repeats weekly"
          },
          "startTime": {
            "type": "string",
            "description": "Start time in HH:mm (legacy, daily, weekly, monthly)."
          },
          "endTime": {
            "type": "string",
            "description": "End time in HH:mm (legacy, daily, weekly, monthly)."
          },
          "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, clamped)."
          },
          "endDayOfMonth": {
            "type": "number",
            "description": "Monthly only: end day-of-month (1-31, clamped)."
          },
          "startIso": {
            "type": "string",
            "description": "One-time only: absolute ISO-8601 start instant."
          },
          "endIso": {
            "type": "string",
            "description": "One-time only: absolute ISO-8601 end instant."
          }
        },
        "required": [
          "active",
          "heatingLevel"
        ]
      },
      "SupportDto": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Support mechanism on (true) or off (false)"
          }
        },
        "required": [
          "enabled"
        ]
      },
      "PathUpdateScheduledHeatingDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID of the schduler that needs to be modified"
          }
        },
        "required": [
          "id"
        ]
      },
      "BodyUpdateScheduledHeatingDto": {
        "type": "object",
        "properties": {
          "recurrence": {
            "type": "string",
            "enum": [
              "oneTime",
              "daily",
              "weekly",
              "monthly"
            ],
            "description": "Recurrence (v2 schedules)."
          },
          "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."
          },
          "active": {
            "type": "boolean",
            "description": "informs if the schedule is active"
          },
          "days": {
            "type": "number",
            "description": "what days the action should be applied. A number that represents a bitmask, where every bit 0-7 corresponds to a day of the week."
          },
          "endTime": {
            "type": "string",
            "description": "time the ofen should stop heating. In HH:mm format"
          },
          "heatingLevel": {
            "type": "number",
            "description": "level the ofen should apply in the schedule"
          },
          "repeat": {
            "type": "boolean",
            "description": "If the schedule is repeated weekly"
          },
          "startTime": {
            "type": "string",
            "description": "time the ofen should start heating. In HH:mm format"
          }
        }
      },
      "AddThermometerDto": {
        "type": "object",
        "properties": {
          "peripheralId": {
            "type": "string",
            "description": "The bluetooth peripheral-id of the thermometer to be paired.",
            "example": "a4b778d309"
          }
        },
        "required": [
          "peripheralId"
        ]
      },
      "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"
        ]
      },
      "TriggerUpdateDto": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string",
            "enum": [
              "software",
              "miner-firmware"
            ],
            "description": "Update category to trigger",
            "example": "software"
          }
        },
        "required": [
          "category"
        ]
      },
      "SetAutoUpdateDto": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string",
            "enum": [
              "software",
              "miner-firmware"
            ],
            "description": "Update category to configure",
            "example": "software"
          },
          "enabled": {
            "type": "boolean",
            "description": "Enable (true) or disable (false) automatic updates",
            "example": true
          }
        },
        "required": [
          "category",
          "enabled"
        ]
      }
    }
  }
}