{
    "swagger": "2.0",
    "info": {
        "description": "Local, read/write REST API for AxeOs-compatible miners (Bitaxe, NerdAxe) on your LAN.\nNo authentication — internal LAN use only.\n\nEndpoints are grouped by the service answering them: dashboard-api (this binary, default :8080);\nremote-dashboard-api (default :8081), a read-only API serving the miners pushed to hashboard.live;\nthe mailer (default :8082, on the loopback only), reached through dashboard-api.",
        "title": "axeos-dashboard API",
        "contact": {},
        "version": "1.0"
    },
    "host": "localhost:8080",
    "basePath": "/",
    "paths": {
        "/api/audit": {
            "get": {
                "description": "Every user-triggered action that matters (miner restart, pool switch, config save, audit export, backups download, network scan) between from (inclusive) and to (exclusive) -- newest first. Both default to the last 24 hours; the range can't exceed a year. API entries carry the client's IP and User-Agent, system entries the service that acted (e.g. the scheduler).",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Audit"
                ],
                "summary": "List audit entries",
                "parameters": [
                    {
                        "type": "string",
                        "description": "range start, RFC 3339 (default: to minus 24h)",
                        "name": "from",
                        "in": "query"
                    },
                    {
                        "type": "string",
                        "description": "range end, RFC 3339 (default: now)",
                        "name": "to",
                        "in": "query"
                    },
                    {
                        "type": "string",
                        "description": "only this miner's entries (its IP) -- a pool switch applied to every miner included",
                        "name": "ip",
                        "in": "query"
                    },
                    {
                        "type": "string",
                        "description": "only this type -- one of GET /api/audit/types",
                        "name": "type",
                        "in": "query"
                    },
                    {
                        "type": "integer",
                        "description": "page number, 1-based (default 1)",
                        "name": "page",
                        "in": "query"
                    },
                    {
                        "type": "integer",
                        "description": "entries per page (default 50, max 200)",
                        "name": "pageSize",
                        "in": "query"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/handler.AuditResponse"
                        }
                    },
                    "400": {
                        "description": "from/to malformed, from not before to, or range over a year",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    },
                    "500": {
                        "description": "the audit log couldn't be read",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/api/audit/export": {
            "get": {
                "description": "Same range and defaults as GET /api/audit (last 24 hours, at most a year), unpaginated, as a JSON file attachment: the resolved range, the export time and the entry count, then every entry.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Audit"
                ],
                "summary": "Export audit entries",
                "parameters": [
                    {
                        "type": "string",
                        "description": "range start, RFC 3339 (default: to minus 24h)",
                        "name": "from",
                        "in": "query"
                    },
                    {
                        "type": "string",
                        "description": "range end, RFC 3339 (default: now)",
                        "name": "to",
                        "in": "query"
                    },
                    {
                        "type": "string",
                        "description": "only this miner's entries (its IP) -- a pool switch applied to every miner included",
                        "name": "ip",
                        "in": "query"
                    },
                    {
                        "type": "string",
                        "description": "only this type -- one of GET /api/audit/types",
                        "name": "type",
                        "in": "query"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/handler.AuditExport"
                        }
                    },
                    "400": {
                        "description": "from/to malformed, from not before to, or range over a year",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    },
                    "500": {
                        "description": "the audit log couldn't be read",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/api/audit/types": {
            "get": {
                "description": "Every type an audit entry can have, in the order the dashboard offers them -- the values GET /api/audit and /api/audit/export accept as type.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Audit"
                ],
                "summary": "List audit entry types",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/handler.AuditTypesResponse"
                        }
                    }
                }
            }
        },
        "/api/backups": {
            "get": {
                "description": "Returns the monthly backup archives, most recent first. The feeder adds each completed (UTC) day to its month's archive, so the current month's archive is complete up to yesterday.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Backups"
                ],
                "summary": "List monthly backups",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/handler.BackupsResponse"
                        }
                    }
                }
            }
        },
        "/api/backups/download": {
            "get": {
                "description": "Returns one zip holding the given months' archives (up to yesterday for the current month): every miner's daily JSONL files under data/bitaxes/ -- unzip into storage.dataDir to restore.",
                "produces": [
                    "application/zip"
                ],
                "tags": [
                    "Backups"
                ],
                "summary": "Download monthly backups",
                "parameters": [
                    {
                        "type": "string",
                        "description": "Comma-separated months, YYYY-MM",
                        "name": "months",
                        "in": "query",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "type": "file"
                        }
                    },
                    "400": {
                        "description": "invalid or missing months",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    },
                    "404": {
                        "description": "no backup for one of the months",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/api/config/discover": {
            "get": {
                "description": "Probes a single IP (?ip=) or sweeps a CIDR range (?cidr=, max /24, defaults to the server's own local /24 subnet when both are omitted). ip takes priority if both are set.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Configuration"
                ],
                "summary": "Discover AxeOS devices on the network",
                "parameters": [
                    {
                        "type": "string",
                        "description": "Probe exactly this one IP instead of scanning a range (e.g. a device on a different VLAN than the server). Takes priority over cidr.",
                        "name": "ip",
                        "in": "query"
                    },
                    {
                        "type": "string",
                        "description": "Network range to sweep, e.g. '192.168.1.0/24' (max /24 = 254 hosts). Omitted: auto-detects and scans the server's own local /24 subnet. Ignored if ip is set.",
                        "name": "cidr",
                        "in": "query"
                    },
                    {
                        "type": "string",
                        "description": "Per-host probe timeout, e.g. '500ms' or '2s' (default 1s, capped at 3s). Raise it and retry if a scan/probe found nothing on a slow network.",
                        "name": "timeout",
                        "in": "query"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/handler.bitaxesResponse"
                        }
                    },
                    "400": {
                        "description": "invalid/too-large cidr, or invalid/too-large timeout",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    },
                    "404": {
                        "description": "no AxeOS device found at the given ip",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/api/config/mail/test": {
            "post": {
                "description": "Has the mailer service send a test mail to every configured recipient through the saved SMTP server.",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Notifications"
                ],
                "summary": "Send a test mail",
                "parameters": [
                    {
                        "description": "Mail language (fr or en, default en)",
                        "name": "request",
                        "in": "body",
                        "schema": {
                            "type": "object",
                            "properties": {
                                "lang": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "type": "object",
                            "properties": {
                                "sentTo": {
                                    "type": "array",
                                    "items": {
                                        "type": "string"
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "SMTP server or recipients not configured",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    },
                    "502": {
                        "description": "the SMTP server refused or couldn't be reached",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    },
                    "503": {
                        "description": "the mailer service isn't running",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/api/config/miners": {
            "get": {
                "description": "Returns every miner entry in the managed miners config, including disabled ones. Read-only -- see POST/DELETE for writing.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Configuration"
                ],
                "summary": "List configured miners",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/handler.bitaxesResponse"
                        }
                    }
                }
            },
            "post": {
                "description": "Upserts one or more miner entries into the managed miners file, matched by MAC. New entries are always saved enabled.",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Configuration"
                ],
                "summary": "Add or update configured miners",
                "parameters": [
                    {
                        "description": "Miner entries to add/update",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/handler.bitaxesResponse"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "the full, updated list of configured miners",
                        "schema": {
                            "$ref": "#/definitions/handler.bitaxesResponse"
                        }
                    },
                    "400": {
                        "description": "invalid request body or entry",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    },
                    "409": {
                        "description": "server not started with -miners -- nowhere safe to write",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    },
                    "500": {
                        "description": "failed to write the miners file",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/api/config/settings": {
            "get": {
                "description": "Returns the settings.yml overrides, the built-in defaults for pool dashboards/firmware repos, and a read-only snapshot of process-launch settings (poll intervals, firmware cache TTL) shown for visibility only.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Configuration"
                ],
                "summary": "Get app settings",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/handler.appSettingsResponse"
                        }
                    }
                }
            },
            "post": {
                "description": "Validates and persists the editable subset of app settings to settings.yml.",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Configuration"
                ],
                "summary": "Save app settings",
                "parameters": [
                    {
                        "description": "Settings to save",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/config.AppSettingsFile"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/handler.appSettingsResponse"
                        }
                    },
                    "400": {
                        "description": "invalid settings",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    },
                    "409": {
                        "description": "server has no resolvable config path -- nowhere safe to write",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    },
                    "500": {
                        "description": "failed to write the app-settings file",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/api/info": {
            "get": {
                "description": "Returns this binary's git SHA, whether it's up to date with the latest GitHub release, the UI feature flags (see config.UIConfig), whether this is remote-dashboard-api (as opposed to dashboard-api), and (remote-dashboard-api only) the hashboard base URL.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Info"
                ],
                "summary": "Server build/version info",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/model.InfoResponse"
                        }
                    }
                }
            }
        },
        "/api/mailer/status": {
            "get": {
                "description": "The mailer's own API, on the loopback only (mailer.port, default 8082): since when it runs, and when its loop last looked for a mail to send (once a minute). Read through dashboard-api's GET /api/status.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Mailer"
                ],
                "summary": "Mailer status",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/main.statusResponse"
                        }
                    }
                }
            }
        },
        "/api/mailer/test": {
            "post": {
                "description": "The mailer's own API, on the loopback only (mailer.port, default 8082): sends a test mail to every recipient through the saved SMTP server. Reached through dashboard-api's POST /api/config/mail/test.",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Mailer"
                ],
                "summary": "Send a test mail (mailer)",
                "parameters": [
                    {
                        "description": "Mail language (fr or en, default en)",
                        "name": "request",
                        "in": "body",
                        "schema": {
                            "$ref": "#/definitions/main.testMailRequest"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/main.testMailResponse"
                        }
                    },
                    "400": {
                        "description": "SMTP server or recipients not configured",
                        "schema": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "integer"
                                },
                                "error": {
                                    "type": "string"
                                },
                                "message": {
                                    "type": "string"
                                }
                            }
                        }
                    },
                    "502": {
                        "description": "the SMTP server refused or couldn't be reached",
                        "schema": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "integer"
                                },
                                "error": {
                                    "type": "string"
                                },
                                "message": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/miners": {
            "get": {
                "description": "Returns every enabled miner with its latest snapshot (hashrate, temp, shares, pool, uptime...).",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Miners"
                ],
                "summary": "List all configured miners",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/model.MinersResponse"
                        }
                    }
                }
            }
        },
        "/api/miners/alerts": {
            "get": {
                "description": "Returns each miner's N most recent alert-bearing entries (today's history only).",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Alerts"
                ],
                "summary": "List the most recent miner alerts",
                "parameters": [
                    {
                        "type": "integer",
                        "description": "max entries to return per miner (default 10)",
                        "name": "limit",
                        "in": "query"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/handler.AlertEntry"
                            }
                        }
                    }
                }
            }
        },
        "/api/miners/alerts/history": {
            "get": {
                "description": "Returns every alert episode (consecutive same-type occurrences collapsed into one) on the given day across all miners, most recently active first, optionally filtered by ip/type.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Alerts"
                ],
                "summary": "List a single day's recorded miner alerts, grouped into episodes and paginated",
                "parameters": [
                    {
                        "type": "string",
                        "description": "the single day to scan (YYYY-MM-DD)",
                        "name": "date",
                        "in": "query",
                        "required": true
                    },
                    {
                        "type": "integer",
                        "description": "page number, 1-based (default 1)",
                        "name": "page",
                        "in": "query"
                    },
                    {
                        "type": "integer",
                        "description": "episodes per page (default 50, max 200)",
                        "name": "pageSize",
                        "in": "query"
                    },
                    {
                        "type": "string",
                        "description": "filter to this miner IP only",
                        "name": "ip",
                        "in": "query"
                    },
                    {
                        "type": "string",
                        "description": "filter to this alert type only",
                        "name": "type",
                        "in": "query"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/handler.AlertHistoryResponse"
                        }
                    }
                }
            }
        },
        "/api/miners/history": {
            "get": {
                "description": "Hashrate of every configured miner over the last 24 hours, averaged into 15-minute buckets (96 points, oldest first). Feeds the dashboard's fleet chart and per-miner sparklines.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Miners"
                ],
                "summary": "Get the last 24h of every miner, downsampled",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/handler.HistoryResponse"
                        }
                    }
                }
            }
        },
        "/api/miners/pool/fallback/enable": {
            "put": {
                "description": "Switches miner(s) to the primary or fallback pool and restarts them to apply it.",
                "tags": [
                    "Miner actions"
                ],
                "summary": "Switch stratum pool",
                "parameters": [
                    {
                        "type": "string",
                        "description": "Miner IP or hostname — omit to apply to all configured miners",
                        "name": "miner",
                        "in": "query"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "No Content"
                    },
                    "502": {
                        "description": "one or more miners didn't apply the change (unreachable, or rejected the request)",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/api/miners/pool/primary/enable": {
            "put": {
                "description": "Switches miner(s) to the primary or fallback pool and restarts them to apply it.",
                "tags": [
                    "Miner actions"
                ],
                "summary": "Switch stratum pool",
                "parameters": [
                    {
                        "type": "string",
                        "description": "Miner IP or hostname — omit to apply to all configured miners",
                        "name": "miner",
                        "in": "query"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "No Content"
                    },
                    "502": {
                        "description": "one or more miners didn't apply the change (unreachable, or rejected the request)",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/api/miners/{hostnameOrIp}/restart": {
            "post": {
                "description": "Restarts the device via its AxeOs restart endpoint.",
                "tags": [
                    "Miner actions"
                ],
                "summary": "Restart a miner",
                "parameters": [
                    {
                        "type": "string",
                        "description": "Miner IP or configured hostname",
                        "name": "hostnameOrIp",
                        "in": "path",
                        "required": true
                    }
                ],
                "responses": {
                    "204": {
                        "description": "No Content"
                    },
                    "404": {
                        "description": "miner not found",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    },
                    "502": {
                        "description": "the miner is unreachable or rejected the restart request",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/api/miners/{hostnameOrIp}/stats": {
            "get": {
                "description": "Returns every JSONL entry (one per poll cycle) of the last 24 hours for a single miner, oldest first, used by the miner panel's history chart. An empty array when nothing was recorded in that time.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Miners"
                ],
                "summary": "Get the last 24h of stats for one miner",
                "parameters": [
                    {
                        "type": "string",
                        "description": "Miner IP or configured hostname",
                        "name": "hostnameOrIp",
                        "in": "path",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/handler.StatsResponse"
                        }
                    },
                    "404": {
                        "description": "miner not found",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/api/remote/board": {
            "get": {
                "description": "Returns what hashboard knows about the board this instance pushes to: the link to its remote dashboard, and when hashboard last received a sample for it. Empty when remote push isn't configured, or hashboard can't be reached. Cached for one feeder interval.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Remote push"
                ],
                "summary": "Remote board info",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/remotepush.Board"
                        }
                    }
                }
            }
        },
        "/api/status": {
            "get": {
                "description": "How each process is doing -- dashboard-api, the feeder (its last poll), the health check, the mailer, the firmware cache: ok, late (nothing within twice its interval), offline (didn't answer) or unknown (no run yet).",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Status"
                ],
                "summary": "Services status",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/handler.StatusResponse"
                        }
                    }
                }
            }
        },
        "/api/{boardId}/miners": {
            "get": {
                "description": "Auto-discovers miners from the board's bitaxes directory — no local config needed, data comes from whatever the feeder already pushed to hashboard.live.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Board miners"
                ],
                "summary": "List miners pushed to a hashboard board (read-only)",
                "parameters": [
                    {
                        "type": "string",
                        "description": "hashboard board ID",
                        "name": "boardId",
                        "in": "path",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/model.MinersResponse"
                        }
                    },
                    "404": {
                        "description": "board not found",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/api/{boardId}/miners/history": {
            "get": {
                "description": "Same as /api/miners/history, for the miners pushed to a hashboard board.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Board miners"
                ],
                "summary": "Get the last 24h of every remote miner, downsampled (read-only)",
                "parameters": [
                    {
                        "type": "string",
                        "description": "hashboard board ID",
                        "name": "boardId",
                        "in": "path",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/handler.HistoryResponse"
                        }
                    },
                    "404": {
                        "description": "board not found",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    }
                }
            }
        },
        "/api/{boardId}/{ip}/stats": {
            "get": {
                "description": "Same as /api/miners/{hostnameOrIp}/stats, for a miner pushed to a hashboard board.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Board miners"
                ],
                "summary": "Get the last 24h of stats for one remote miner (read-only)",
                "parameters": [
                    {
                        "type": "string",
                        "description": "hashboard board ID",
                        "name": "boardId",
                        "in": "path",
                        "required": true
                    },
                    {
                        "type": "string",
                        "description": "Miner IP",
                        "name": "ip",
                        "in": "path",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/handler.StatsResponse"
                        }
                    },
                    "500": {
                        "description": "data file missing or unreadable",
                        "schema": {
                            "$ref": "#/definitions/handler.ErrorResponse"
                        }
                    }
                }
            }
        }
    },
    "definitions": {
        "audit.Change": {
            "type": "object",
            "properties": {
                "after": {
                    "type": "string"
                },
                "before": {
                    "type": "string"
                },
                "field": {
                    "type": "string"
                },
                "kind": {
                    "$ref": "#/definitions/audit.ChangeKind"
                }
            }
        },
        "audit.ChangeKind": {
            "type": "string",
            "enum": [
                "changed",
                "added",
                "removed"
            ],
            "x-enum-varnames": [
                "ChangeChanged",
                "ChangeAdded",
                "ChangeRemoved"
            ]
        },
        "audit.Entry": {
            "type": "object",
            "properties": {
                "changes": {
                    "description": "What changed, before and after -- for the security-relevant types\n(see changes.go).",
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/audit.Change"
                    }
                },
                "cron": {
                    "type": "string"
                },
                "error": {
                    "description": "Why it failed, whatever its source.",
                    "type": "string"
                },
                "ip": {
                    "description": "API only: there's no authentication, so the client's IP and\nUser-Agent are the closest thing to \"who\" this tool can record.",
                    "type": "string"
                },
                "query": {
                    "description": "The request's query string, unescaped -- what was asked for (the\nmonths downloaded, the exported range, the miner a pool switch\ntargeted...).",
                    "type": "string"
                },
                "requestId": {
                    "type": "string"
                },
                "service": {
                    "description": "System only.",
                    "type": "string"
                },
                "source": {
                    "$ref": "#/definitions/audit.Source"
                },
                "status": {
                    "type": "integer"
                },
                "target": {
                    "description": "Target is the IP of the miner acted on -- empty for a config save,\nor a pool switch applied to every miner.",
                    "type": "string"
                },
                "ts": {
                    "type": "string"
                },
                "type": {
                    "type": "string"
                },
                "userAgent": {
                    "type": "string"
                }
            }
        },
        "audit.Source": {
            "type": "string",
            "enum": [
                "api",
                "system"
            ],
            "x-enum-varnames": [
                "SourceAPI",
                "SourceSystem"
            ]
        },
        "backup.Info": {
            "type": "object",
            "properties": {
                "checksum": {
                    "description": "Checksum is the archive's MD5, set once the month is final.",
                    "type": "string"
                },
                "month": {
                    "type": "string"
                },
                "size": {
                    "type": "integer"
                },
                "updatedAt": {
                    "type": "string"
                }
            }
        },
        "config.AppSettingsFile": {
            "type": "object",
            "properties": {
                "electricity": {
                    "$ref": "#/definitions/config.ElectricityConfig"
                },
                "firmware": {
                    "$ref": "#/definitions/config.AppSettingsFirmware"
                },
                "notifications": {
                    "$ref": "#/definitions/config.NotificationsConfig"
                },
                "pools": {
                    "$ref": "#/definitions/config.PoolsConfig"
                },
                "remote": {
                    "$ref": "#/definitions/config.RemoteConfig"
                }
            }
        },
        "config.AppSettingsFirmware": {
            "type": "object",
            "properties": {
                "repos": {
                    "type": "object",
                    "additionalProperties": {
                        "type": "string"
                    }
                }
            }
        },
        "config.AuditReportConfig": {
            "type": "object",
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "frequency": {
                    "$ref": "#/definitions/config.ReportFrequency"
                },
                "skipIfEmpty": {
                    "description": "SkipIfEmpty sends nothing for a period nothing was recorded in.",
                    "type": "boolean"
                },
                "types": {
                    "description": "Types narrows it to these audit entry types -- all of them when empty.",
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                }
            }
        },
        "config.Bitaxe": {
            "type": "object",
            "properties": {
                "alias": {
                    "description": "Alias is an optional operator-set override for the display name shown\nin the UI wherever Hostname would otherwise be shown (see\nDisplayName). Unlike Hostname -- which a network discovery re-probe\n(see internal/discovery, and the \"select an already-configured\ndevice\" refresh flow in the Settings UI) silently overwrites with\nwhatever the device itself reports -- Alias is never touched by\ndiscovery, so it's the one place a custom, human-chosen name survives\na refresh.",
                    "type": "string"
                },
                "enabled": {
                    "type": "boolean"
                },
                "fallbackPort": {
                    "type": "integer"
                },
                "fallbackUrl": {
                    "type": "string"
                },
                "fallbackUser": {
                    "type": "string"
                },
                "hostname": {
                    "type": "string"
                },
                "ip": {
                    "type": "string"
                },
                "mac": {
                    "description": "Mac is the device's MAC address (colons/hyphens optional, normalized\nby StorageKey) -- the storage-directory key, stable across IP/\nlocation changes unlike Ip. Populated either by hand or by the network\ndiscovery flow (see internal/discovery), which reads it straight off\nthe device's own /api/system/info response.",
                    "type": "string"
                },
                "model": {
                    "$ref": "#/definitions/config.Model"
                },
                "port": {
                    "type": "integer"
                },
                "schedule": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/config.CronSchedule"
                    }
                },
                "url": {
                    "type": "string"
                },
                "user": {
                    "type": "string"
                }
            }
        },
        "config.CronSchedule": {
            "type": "object",
            "properties": {
                "action": {
                    "$ref": "#/definitions/config.ScheduleAction"
                },
                "cron": {
                    "type": "string"
                }
            }
        },
        "config.ElectricityConfig": {
            "type": "object",
            "properties": {
                "devices": {
                    "description": "Devices are the non-miner power consumers (fan, router...), declared by\nhand since nothing can poll them -- so the installation's figures\nthey feed are only ever an informative estimate, kept apart from the\nminers' measured ones.",
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/config.PowerDevice"
                    }
                },
                "ratePerKwh": {
                    "description": "RatePerKwh is the electricity cost in euros per kilowatt-hour (e.g. 0.1915 for 19.15 cts/kWh).",
                    "type": "number"
                }
            }
        },
        "config.MailConfig": {
            "type": "object",
            "properties": {
                "smtp": {
                    "$ref": "#/definitions/config.SMTPConfig"
                },
                "to": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                }
            }
        },
        "config.Model": {
            "type": "string",
            "enum": [
                "bitaxe",
                "nerdaxe"
            ],
            "x-enum-varnames": [
                "ModelBitaxe",
                "ModelNerdaxe"
            ]
        },
        "config.NotificationsConfig": {
            "type": "object",
            "properties": {
                "auditReport": {
                    "$ref": "#/definitions/config.AuditReportConfig"
                },
                "dashboardURL": {
                    "description": "DashboardURL is the dashboard's own address as reached from where the\nmessages are read (e.g. http://192.168.1.60) -- for their links.",
                    "type": "string"
                },
                "language": {
                    "description": "Language is the messages' language: \"fr\" or \"en\" (the default).",
                    "type": "string"
                },
                "mail": {
                    "$ref": "#/definitions/config.MailConfig"
                }
            }
        },
        "config.PoolsConfig": {
            "type": "object",
            "properties": {
                "dashboards": {
                    "description": "Dashboards maps a stratum pool hostname to its web dashboard URL\ntemplate. Use {user} as placeholder — replaced by the account part\nof the stratum user (before the first dot). Example:\n\"stratum.braiins.com\" -\u003e \"https://pool.braiins.com/mining/overview/{user}\"",
                    "type": "object",
                    "additionalProperties": {
                        "type": "string"
                    }
                }
            }
        },
        "config.PowerDevice": {
            "type": "object",
            "properties": {
                "name": {
                    "type": "string"
                },
                "power": {
                    "description": "Power is the device's constant draw, in watts.",
                    "type": "number"
                }
            }
        },
        "config.RemoteConfig": {
            "type": "object",
            "properties": {
                "apiKey": {
                    "type": "string"
                },
                "pushURL": {
                    "type": "string"
                }
            }
        },
        "config.ReportFrequency": {
            "type": "string",
            "enum": [
                "daily",
                "every2days",
                "weekly",
                "monthly"
            ],
            "x-enum-comments": {
                "ReportDaily": "every night: the day before",
                "ReportEvery2Days": "every other night: the 2 days before",
                "ReportMonthly": "the night of the 1st: the month before",
                "ReportWeekly": "Sunday to Monday night: Monday to Sunday"
            },
            "x-enum-descriptions": [
                "every night: the day before",
                "every other night: the 2 days before",
                "Sunday to Monday night: Monday to Sunday",
                "the night of the 1st: the month before"
            ],
            "x-enum-varnames": [
                "ReportDaily",
                "ReportEvery2Days",
                "ReportWeekly",
                "ReportMonthly"
            ]
        },
        "config.SMTPConfig": {
            "type": "object",
            "properties": {
                "from": {
                    "type": "string"
                },
                "host": {
                    "type": "string"
                },
                "password": {
                    "description": "Password is write-only through the API: it's stored here, but GET\n/api/config/settings only ever says whether one is set (see\nhandler.appSettingsResponse).",
                    "type": "string"
                },
                "port": {
                    "type": "integer"
                },
                "security": {
                    "$ref": "#/definitions/config.SMTPSecurity"
                },
                "username": {
                    "type": "string"
                }
            }
        },
        "config.SMTPSecurity": {
            "type": "string",
            "enum": [
                "starttls",
                "tls",
                "none"
            ],
            "x-enum-varnames": [
                "SMTPStartTLS",
                "SMTPTLS",
                "SMTPNone"
            ]
        },
        "config.ScheduleAction": {
            "type": "string",
            "enum": [
                "switch_primary",
                "switch_fallback",
                "restart"
            ],
            "x-enum-varnames": [
                "ActionSwitchPrimary",
                "ActionSwitchFallback",
                "ActionRestart"
            ]
        },
        "handler.AlertEntry": {
            "type": "object",
            "properties": {
                "alerts": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/model.Alert"
                    }
                },
                "hostname": {
                    "type": "string"
                },
                "minerIp": {
                    "type": "string"
                },
                "minerMac": {
                    "type": "string"
                },
                "timestamp": {
                    "type": "string"
                }
            }
        },
        "handler.AlertEpisode": {
            "type": "object",
            "properties": {
                "firstSeen": {
                    "type": "string"
                },
                "hostname": {
                    "type": "string"
                },
                "lastSeen": {
                    "type": "string"
                },
                "message": {
                    "type": "string"
                },
                "minerIp": {
                    "type": "string"
                },
                "minerMac": {
                    "type": "string"
                },
                "occurrences": {
                    "type": "integer"
                },
                "peakValue": {
                    "type": "number"
                },
                "threshold": {
                    "type": "number"
                },
                "type": {
                    "type": "string"
                }
            }
        },
        "handler.AlertHistoryResponse": {
            "type": "object",
            "properties": {
                "episodes": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/handler.AlertEpisode"
                    }
                },
                "page": {
                    "type": "integer"
                },
                "pageSize": {
                    "type": "integer"
                },
                "total": {
                    "type": "integer"
                }
            }
        },
        "handler.AuditExport": {
            "type": "object",
            "properties": {
                "entries": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/audit.Entry"
                    }
                },
                "exportedAt": {
                    "type": "string"
                },
                "from": {
                    "type": "string"
                },
                "ip": {
                    "type": "string"
                },
                "to": {
                    "type": "string"
                },
                "total": {
                    "type": "integer"
                },
                "type": {
                    "type": "string"
                }
            }
        },
        "handler.AuditResponse": {
            "type": "object",
            "properties": {
                "entries": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/audit.Entry"
                    }
                },
                "page": {
                    "type": "integer"
                },
                "pageSize": {
                    "type": "integer"
                },
                "total": {
                    "type": "integer"
                }
            }
        },
        "handler.AuditTypesResponse": {
            "type": "object",
            "properties": {
                "types": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                }
            }
        },
        "handler.BackupsResponse": {
            "type": "object",
            "properties": {
                "data": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/backup.Info"
                    }
                }
            }
        },
        "handler.ErrorResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer"
                },
                "error": {
                    "type": "string"
                },
                "message": {
                    "type": "string"
                }
            }
        },
        "handler.HistoryResponse": {
            "type": "object",
            "properties": {
                "bucketSeconds": {
                    "type": "integer"
                },
                "from": {
                    "type": "string"
                },
                "miners": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/handler.MinerHistory"
                    }
                }
            }
        },
        "handler.MinerHistory": {
            "type": "object",
            "properties": {
                "hashRate": {
                    "description": "TH/s",
                    "type": "array",
                    "items": {
                        "type": "number"
                    }
                },
                "ip": {
                    "type": "string"
                }
            }
        },
        "handler.ServiceStatus": {
            "type": "object",
            "properties": {
                "interval": {
                    "type": "string"
                },
                "lastRunAt": {
                    "type": "string"
                },
                "startedAt": {
                    "type": "string"
                },
                "state": {
                    "type": "string"
                },
                "version": {
                    "type": "string"
                }
            }
        },
        "handler.ServicesStatus": {
            "type": "object",
            "properties": {
                "dashboardApi": {
                    "$ref": "#/definitions/handler.ServiceStatus"
                },
                "feeder": {
                    "$ref": "#/definitions/handler.ServiceStatus"
                },
                "firmwareCache": {
                    "description": "FirmwareCache is the feeder's check for new firmware releases, once\nper its TTL.",
                    "allOf": [
                        {
                            "$ref": "#/definitions/handler.ServiceStatus"
                        }
                    ]
                },
                "healthCheck": {
                    "$ref": "#/definitions/handler.ServiceStatus"
                },
                "mailer": {
                    "$ref": "#/definitions/handler.ServiceStatus"
                }
            }
        },
        "handler.StatsResponse": {
            "type": "object",
            "properties": {
                "data": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/model.MinerInfo"
                    }
                },
                "total": {
                    "type": "integer"
                }
            }
        },
        "handler.StatusResponse": {
            "type": "object",
            "properties": {
                "checkedAt": {
                    "type": "string"
                },
                "services": {
                    "$ref": "#/definitions/handler.ServicesStatus"
                }
            }
        },
        "handler.appSettingsDefaults": {
            "type": "object",
            "properties": {
                "firmware": {
                    "$ref": "#/definitions/config.AppSettingsFirmware"
                },
                "pools": {
                    "$ref": "#/definitions/config.PoolsConfig"
                }
            }
        },
        "handler.appSettingsReadOnly": {
            "type": "object",
            "properties": {
                "auditReport": {
                    "description": "AuditReport is the feeder's record of the daily audit mail (see\nauditreport.State), read back from disk -- the feeder sends it.",
                    "allOf": [
                        {
                            "$ref": "#/definitions/handler.auditReportStatus"
                        }
                    ]
                },
                "feederInterval": {
                    "type": "string"
                },
                "firmwareCacheCheckedAt": {
                    "description": "FirmwareCacheCheckedAt is the most recent CheckedAt across every\nmodel in the firmware cache (RFC3339, UTC) -- omitted if the cache\nis empty (nothing checked yet, e.g. fresh install or no miner has\ntriggered a check). Lets the UI show \"was this actually run\" rather\nthan just the configured TTL.",
                    "type": "string"
                },
                "firmwareCacheTTL": {
                    "type": "string"
                },
                "healthCheckInterval": {
                    "type": "string"
                }
            }
        },
        "handler.appSettingsResponse": {
            "type": "object",
            "properties": {
                "defaults": {
                    "$ref": "#/definitions/handler.appSettingsDefaults"
                },
                "electricity": {
                    "$ref": "#/definitions/config.ElectricityConfig"
                },
                "firmware": {
                    "$ref": "#/definitions/config.AppSettingsFirmware"
                },
                "lastUpdated": {
                    "type": "string"
                },
                "notifications": {
                    "description": "Notifications shadows the embedded AppSettingsFile.Notifications: same\nJSON key, but a shape with no SMTP password field at all -- it never\nleaves the server.",
                    "allOf": [
                        {
                            "$ref": "#/definitions/handler.notificationsResponse"
                        }
                    ]
                },
                "pools": {
                    "$ref": "#/definitions/config.PoolsConfig"
                },
                "readOnly": {
                    "$ref": "#/definitions/handler.appSettingsReadOnly"
                },
                "remote": {
                    "$ref": "#/definitions/config.RemoteConfig"
                }
            }
        },
        "handler.auditReportSettingsResponse": {
            "type": "object",
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "frequency": {
                    "$ref": "#/definitions/config.ReportFrequency"
                },
                "skipIfEmpty": {
                    "description": "SkipIfEmpty sends nothing for a period nothing was recorded in.",
                    "type": "boolean"
                },
                "types": {
                    "description": "Types is never null, unlike the config's (empty = every type).",
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                }
            }
        },
        "handler.auditReportStatus": {
            "type": "object",
            "properties": {
                "lastAttemptAt": {
                    "type": "string"
                },
                "lastError": {
                    "type": "string"
                },
                "lastFrom": {
                    "description": "LastFrom/LastTo is the period last mailed (\"2006-01-02\", To\nexcluded).",
                    "type": "string"
                },
                "lastSentAt": {
                    "type": "string"
                },
                "lastTo": {
                    "type": "string"
                }
            }
        },
        "handler.bitaxesResponse": {
            "type": "object",
            "properties": {
                "bitaxes": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/config.Bitaxe"
                    }
                },
                "lastUpdated": {
                    "type": "string"
                }
            }
        },
        "handler.mailSettingsResponse": {
            "type": "object",
            "properties": {
                "smtp": {
                    "$ref": "#/definitions/handler.smtpSettingsResponse"
                },
                "to": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                }
            }
        },
        "handler.notificationsResponse": {
            "type": "object",
            "properties": {
                "auditReport": {
                    "$ref": "#/definitions/handler.auditReportSettingsResponse"
                },
                "dashboardURL": {
                    "type": "string"
                },
                "language": {
                    "type": "string"
                },
                "mail": {
                    "$ref": "#/definitions/handler.mailSettingsResponse"
                }
            }
        },
        "handler.smtpSettingsResponse": {
            "type": "object",
            "properties": {
                "from": {
                    "type": "string"
                },
                "host": {
                    "type": "string"
                },
                "passwordSet": {
                    "description": "PasswordSet is all that's ever said about the password.",
                    "type": "boolean"
                },
                "port": {
                    "type": "integer"
                },
                "security": {
                    "$ref": "#/definitions/config.SMTPSecurity"
                },
                "username": {
                    "type": "string"
                }
            }
        },
        "main.statusResponse": {
            "type": "object",
            "properties": {
                "lastCheckAt": {
                    "description": "LastCheckAt is the last time the mailer looked for a mail to send --\nonce a minute, so an old one means its loop is stuck.",
                    "type": "string"
                },
                "startedAt": {
                    "type": "string"
                }
            }
        },
        "main.testMailRequest": {
            "type": "object",
            "properties": {
                "lang": {
                    "description": "Lang picks the mail's language, the dashboard's own (\"fr\" or \"en\").",
                    "type": "string"
                }
            }
        },
        "main.testMailResponse": {
            "type": "object",
            "properties": {
                "sentTo": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                }
            }
        },
        "model.Alert": {
            "type": "object",
            "properties": {
                "message": {
                    "type": "string"
                },
                "threshold": {
                    "description": "the threshold in effect at calculation time, omitted otherwise",
                    "type": "number"
                },
                "type": {
                    "description": "\"tempHigh\", \"fanHigh\", \"offline\", \"macMismatch\", \"firmwareUpdate\"",
                    "type": "string"
                },
                "value": {
                    "description": "the reading that triggered it (temp/fan), omitted otherwise",
                    "type": "number"
                }
            }
        },
        "model.InfoResponse": {
            "type": "object",
            "properties": {
                "appVersion": {
                    "description": "AppVersion is the semver this binary was published under (e.g.\n\"0.1.0\"), only set on a tagged-release build -- \"dev\" (the zero\nvalue's meaning) everywhere else, including the rolling \"latest\"\nbuild. The UI shows this instead of BuildSHA when it's a real\nversion, and falls back to BuildSHA otherwise -- see\ninternal/version.Version.",
                    "type": "string"
                },
                "appVersionReleaseURL": {
                    "type": "string"
                },
                "appVersionStatus": {
                    "description": "Whether this dashboard-api/remote-dashboard-api build itself is up to\ndate with GitHub's \"latest\" release (\"unknown\" | \"upToDate\" |\n\"updateAvailable\") — see internal/appversion. Checked server-side at\nmost once a day, so every client reads the same cached answer.",
                    "type": "string"
                },
                "buildSHA": {
                    "description": "Git commit this binary was built from",
                    "type": "string"
                },
                "hashboardURL": {
                    "description": "HashboardURL is the base URL of the hashboard instance backing this\nremote-dashboard-api (remote-dashboard-api only, config.HashboardURL)\n— lets the UI build a link to the board owner's hashboard account page.",
                    "type": "string"
                },
                "remote": {
                    "description": "Remote is true for remote-dashboard-api, false for dashboard-api --\nthe single React codebase can't otherwise tell which binary it's\ntalking to (both serve the same routes, and hashboardURL alone isn't\na reliable signal: remote-dashboard-api can leave it unconfigured\ntoo, see resources/remote-dashboard.yml). The UI needs this to know\nwhether an unrecognized single path segment is even worth treating\nas a possible board id in the first place -- on dashboard-api, no\nboard ever exists, so a mistyped URL must never surface board-only\nchrome (the public/private chip, the \"board is private\" screen).",
                    "type": "boolean"
                },
                "ui": {
                    "description": "UI mirrors config.UIConfig (each value normalized, so the frontend\nnever sees an empty string) -- the single React codebase shows\neverything unless a flag here says otherwise, instead of hardcoding\nwhat \"local\" vs \"remote\" means.",
                    "allOf": [
                        {
                            "$ref": "#/definitions/model.UIFeatures"
                        }
                    ]
                }
            }
        },
        "model.MinerInfo": {
            "type": "object",
            "properties": {
                "alerts": {
                    "description": "Alerts is whatever the feeder computed and persisted at the poll that\nproduced this snapshot (temp/fan out of range, offline, mac mismatch,\nfirmware update) -- the current state as of Timestamp, not a live value.",
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/model.Alert"
                    }
                },
                "alias": {
                    "description": "Operator-set override for Hostname (see config.Bitaxe.DisplayName) -- empty when not set, UI falls back to Hostname",
                    "type": "string"
                },
                "alive": {
                    "description": "Health check",
                    "type": "boolean"
                },
                "aliveCheckedAt": {
                    "type": "string"
                },
                "bestDiff": {
                    "description": "Highest difficulty value ever solved by the miner since start‑up",
                    "type": "integer"
                },
                "blockFound": {
                    "type": "integer"
                },
                "deviceModel": {
                    "description": "Computed device model (e.g. \"NerdQAxe++\", \"Bitaxe 602\")",
                    "type": "string"
                },
                "electricityRatePerKwh": {
                    "description": "Electricity cost",
                    "type": "number"
                },
                "energyJPerTh": {
                    "description": "Energy consumption per terahash (Joules per TH)",
                    "type": "number"
                },
                "error": {
                    "description": "Error surfaces a serious inconsistency the UI should call out\ndistinctly from a normal offline/unreachable state -- currently just\na mismatch between the configured mac: and what the device itself\nreports (wrong device at this IP, or a config typo).",
                    "type": "string"
                },
                "fallbackStratumDashboardURL": {
                    "description": "Web dashboard URL for the fallback pool",
                    "type": "string"
                },
                "fallbackStratumPort": {
                    "type": "integer"
                },
                "fallbackStratumURL": {
                    "type": "string"
                },
                "fallbackStratumUser": {
                    "type": "string"
                },
                "fanspeed": {
                    "description": "Fan speed (linked to the Temp field)",
                    "type": "number"
                },
                "hashRateTHs": {
                    "description": "Additional fields requested by the API",
                    "type": "number"
                },
                "hostname": {
                    "description": "Human-readable name from config",
                    "type": "string"
                },
                "ip": {
                    "description": "Miner IP address (derived from the folder name)",
                    "type": "string"
                },
                "isUsingFallbackStratum": {
                    "type": "integer"
                },
                "latestVersion": {
                    "description": "Latest firmware version available on GitHub",
                    "type": "string"
                },
                "macAddr": {
                    "description": "Mac address",
                    "type": "string"
                },
                "networkDifficulty": {
                    "description": "Current network difficulty reported by the pool (unitless)",
                    "type": "integer"
                },
                "power": {
                    "description": "Watts (Joules per second)",
                    "type": "number"
                },
                "releaseURL": {
                    "description": "GitHub releases page for the latest firmware version",
                    "type": "string"
                },
                "responseTime": {
                    "description": "Ping/latency to the pool (ms).",
                    "type": "number"
                },
                "sharesAccepted": {
                    "type": "integer"
                },
                "sharesRejected": {
                    "type": "integer"
                },
                "stratumDashboardURL": {
                    "description": "Web dashboard URL for the primary pool",
                    "type": "string"
                },
                "stratumPort": {
                    "description": "Port of the primary Stratum pool",
                    "type": "integer"
                },
                "stratumURL": {
                    "description": "Pool urls -- Stratum(Port|Fallback...) is what the device itself is\ncurrently connected with, straight off its own /api/system/info\n(see handler.PayloadStructure) -- not to be confused with the\n*configured* pool (config.Bitaxe.Url/Port/...), which the UI reads\nseparately from GET /api/config/miners and compares against these to\nflag drift (see ui/src/utils/poolDrift.ts).",
                    "type": "string"
                },
                "stratumUser": {
                    "description": "Username (typically miner ID) for the primary pool",
                    "type": "string"
                },
                "temp": {
                    "description": "Primary temperature sensor reading (°C)",
                    "type": "number"
                },
                "timestamp": {
                    "type": "string"
                },
                "totalElectricityCost": {
                    "description": "TotalElectricityCost is this miner's cumulative cost (in the\noperator's own currency, see settings.yml's electricity.ratePerKwh)\nsince it has been tracked -- each poll's slice uses the rate that was\nactually in effect at that poll (RawSample.ElectricityRate), not\ntoday's rate, so a later rate change never rewrites past cost. Not\nshown per-miner in the UI -- summed across the fleet for the\n\"Électricité\" KPI card's all-time figure instead.",
                    "type": "number"
                },
                "totalSharesAccepted": {
                    "type": "integer"
                },
                "totalSharesRejected": {
                    "type": "integer"
                },
                "totalUptimeSeconds": {
                    "description": "Total* are persistent, reboot-surviving counters (see\ninternal/storage.Totals) -- unlike SharesAccepted/SharesRejected and\nUptimeSeconds above, which reset whenever the device itself reboots,\nthese keep accumulating for as long as the miner has been tracked.\nZero until the feeder has written at least one totals.json for this\nminer, or until the backfill tool has run.",
                    "type": "integer"
                },
                "updateAvailable": {
                    "description": "True when LatestVersion != Version",
                    "type": "boolean"
                },
                "uptimeSeconds": {
                    "description": "Miner uptime in seconds",
                    "type": "integer"
                },
                "version": {
                    "description": "Firmware version (e.g. \"v2.12.2\")",
                    "type": "string"
                },
                "voltage": {
                    "description": "Volts (millivolts)",
                    "type": "number"
                }
            }
        },
        "model.MinersResponse": {
            "type": "object",
            "properties": {
                "boardPublic": {
                    "description": "BoardPublic reflects hashboard's Account.Public flag for this board\n(remote-dashboard-api only) — meaningless/always false for local\ndashboard-api, which has no such concept and whose UI never reads it.",
                    "type": "boolean"
                },
                "configured": {
                    "description": "Number of miners in config",
                    "type": "integer"
                },
                "devices": {
                    "description": "Echoed from settings so the dashboard needs no second request. Nothing\nabout them is tracked over time: a constant declared draw can't give a\nreal cumulative cost, only an informative estimate.",
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/config.PowerDevice"
                    }
                },
                "miners": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/model.MinerInfo"
                    }
                },
                "total": {
                    "description": "Number of miners successfully loaded",
                    "type": "integer"
                }
            }
        },
        "model.UIActionFeatures": {
            "type": "object",
            "properties": {
                "minerPoolSwitch": {
                    "type": "string"
                },
                "minerRestart": {
                    "description": "MinerRestart/MinerPoolSwitch: \"enabled\" | \"readonly\" | \"hidden\" --\nsee config.UIActionConfig. \"readonly\" renders the button disabled\n(with a hint), \"hidden\" doesn't render it at all.",
                    "type": "string"
                }
            }
        },
        "model.UIFeatures": {
            "type": "object",
            "properties": {
                "action": {
                    "$ref": "#/definitions/model.UIActionFeatures"
                },
                "page": {
                    "$ref": "#/definitions/model.UIPageFeatures"
                }
            }
        },
        "model.UIPageFeatures": {
            "type": "object",
            "properties": {
                "audit": {
                    "description": "Audit: \"enabled\" | \"hidden\" -- see config.UIPageConfig.",
                    "type": "string"
                },
                "backups": {
                    "description": "Backups: \"enabled\" | \"hidden\" -- see config.UIPageConfig.",
                    "type": "string"
                },
                "settings": {
                    "description": "Settings: \"enabled\" | \"readonly\" | \"hidden\" -- see config.UIPageConfig.",
                    "type": "string"
                }
            }
        },
        "remotepush.Board": {
            "type": "object",
            "properties": {
                "boardId": {
                    "type": "string"
                },
                "dashboardURL": {
                    "type": "string"
                },
                "hashboardURL": {
                    "type": "string"
                },
                "lastSampleAt": {
                    "type": "string"
                },
                "public": {
                    "type": "boolean"
                }
            }
        }
    },
    "tags": [
        {
            "description": "Build and version, UI feature flags -- served by dashboard-api and remote-dashboard-api alike.",
            "name": "Info"
        },
        {
            "description": "The configured miners' latest snapshot, history and stats.",
            "name": "Miners"
        },
        {
            "description": "Restart a miner, switch its pool -- proxied to the device.",
            "name": "Miner actions"
        },
        {
            "description": "Recent alerts and their history, grouped into episodes.",
            "name": "Alerts"
        },
        {
            "description": "Every user-triggered action and scheduled run, its types and export.",
            "name": "Audit"
        },
        {
            "description": "The monthly archives of the miners' history.",
            "name": "Backups"
        },
        {
            "description": "miners.yml and settings.yml, read and saved; network discovery.",
            "name": "Configuration"
        },
        {
            "description": "Mail: the test mail, sent by the mailer.",
            "name": "Notifications"
        },
        {
            "description": "How every process of the dashboard is doing.",
            "name": "Status"
        },
        {
            "description": "The hashboard board this dashboard pushes to.",
            "name": "Remote push"
        },
        {
            "description": "A board's miners, their history and stats.",
            "name": "Board miners"
        },
        {
            "description": "The mailer's own API, on the loopback only.",
            "name": "Mailer"
        }
    ],
    "x-tagGroups": [
        {
            "name": "dashboard-api",
            "tags": [
                "Info",
                "Miners",
                "Miner actions",
                "Alerts",
                "Audit",
                "Backups",
                "Configuration",
                "Notifications",
                "Status",
                "Remote push"
            ]
        },
        {
            "name": "remote-dashboard-api",
            "tags": [
                "Board miners"
            ]
        },
        {
            "name": "mailer",
            "tags": [
                "Mailer"
            ]
        }
    ]
}