{
  "openapi": "3.0.4",
  "info": {
    "title": "Kombine Flex Portal — Public API",
    "description": "Anonymous status, cached statistics, purchase map and icon images. **No token or login is required.**\r\n\r\n- The hostname selects the tenant; callers cannot select another tenant.\r\n- Embed the anonymous overview cards directly in an external website with the shared JavaScript module `/docs/widgets.mjs` (no iframe). See [live widget examples and copyable HTML](../docs/widgets) and the [integration instructions](../docs#widgets). `GetPublicWidgetPresentation` supplies localized text and the site's palette. Public widget reads (presentation, statistics including the purchase SVG, scoped progress and machine errors) allow arbitrary origins, GET and no credentials; manager/session CORS stays restricted.\r\n- Status does not check database readiness.\r\n- Windows app downloads: the primary page is `https://{tenant}.kombine.technology/download`; this API also serves `/download`. `DownloadPortalWindowsAppInstaller` supplies the update file and `DownloadPortalWindowsPackage` serves staged signed MSIX files. No login is needed to download; app use retains normal portal authorization. Missing releases return 404. See [app downloads](../docs#windows-downloads).\r\n- `GetIconFromSet`, `GetIconImageFromSet` and `GetIconImageWithBackgroundFromSet` require `/api/v1/icon/{iconSet}/{kid}.{format}` (optionally add size/background before the extension). `kid` is a canonical `Kid.ToString()`; `Kid.Icons[0]` selects the main icon and `Kid.Icons[1]` the under-icon (missing/none omits it; later entries are ignored) and `Kid.Count` supplies the badge, `Kid.Color` the low 24 RGB bits (0 = black), and `Kid.Text` the label. An exact eIcon name is the fallback with count zero, black and empty text. No separate count/color/text/sub parameters or routes without a set remain. See the [changelog](../docs#changelog) for migration.\r\n- Choose `line` or `g` explicitly. Each main/under-icon tries the selected set first, then other packaged sets for the same identity. Unknown sets or assets absent from every set return 404. Malformed KIDs/unknown names return 400. Rendering uses disk caching and no database or external server; KID metadata does not grant access to business data.\r\n- Try the [local icon test page](../docs/icons) to compare palettes, text, under-icons and sizes.\r\n- `GetKombineLogo`, `GetKombineText` and `GetKombineLogoText` (plus `Sized` and `WithBackground` variants) serve local logo SVGs under `/api/v1/logos/...`. Original path geometry, no graphics/font library or database calls. See [logo examples](../docs#logos).\r\n- `GetCircleGradient`, `GetCircleProgress` and `GetCircleRunning` (plus `Sized` variants) render public parameter-only SVGs under `/api/v1/circles/...`. All inputs are path segments. They use local .NET XML/geometry with no graphics library, database or external assets. See [circle examples](../docs#circles).\r\n- `GetLinearGradient` and `GetLinearGradientSized` render full-canvas linear SVG gradients under `/api/v1/gradients/linear/...`, with 2–4 colors and a clockwise angle in degrees (0 points right, 90 down). Public, no login or database. See [gradient examples](../docs#gradients).\r\n- See each operation for caching, limitations and error responses.\r\n\r\n**Integration guides:** [English](../docs) · [Dansk](../docs/da) · [Español](../docs/es)",
    "version": "v1"
  },
  "paths": {
    "/download": {
      "get": {
        "tags": [
          "App downloads"
        ],
        "summary": "Displays Windows downloads for this tenant, or an explicit unavailable state.",
        "description": "Public HTML, no manager token or database access. Uses the portal's ten browser-selected languages, with English as the unsupported-language default. Installation still requires normal portal login; downloading grants no business permissions.",
        "operationId": "GetPortalAppDownloadPage",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    },
    "/download/windows/{architecture}/portal.appinstaller": {
      "get": {
        "tags": [
          "App downloads"
        ],
        "summary": "Downloads a tenant-bound Windows App Installer file with update checks at launch.",
        "description": "Public. architecture is x64 or arm64. Download and open the file with Windows App Installer. 404 means no validated release is staged for this site's tenant/environment/architecture. Canonical HTTPS URLs come from trusted site configuration, never the request host. Beta and production have separate identities. Requires a trusted package signature and WebView2 Runtime on the device.",
        "operationId": "DownloadPortalWindowsAppInstaller",
        "parameters": [
          {
            "name": "architecture",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/appinstaller": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/appinstaller": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/download/windows/{architecture}/{fileName}": {
      "get": {
        "tags": [
          "App downloads"
        ],
        "summary": "Downloads an immutable signed Windows package, with byte-range and conditional request support.",
        "description": "Public binary download. Only exact filenames from operator-staged release metadata are accepted. 404 means missing, invalid or a different tenant/environment/architecture. Does not enumerate directories or accept arbitrary filesystem paths.",
        "operationId": "DownloadPortalWindowsPackage",
        "parameters": [
          {
            "name": "architecture",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fileName",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/msix": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "206": {
            "description": "Partial Content",
            "content": {
              "application/msix": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/msix": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/circles/gradient/{colors}.svg": {
      "get": {
        "tags": [
          "Circles"
        ],
        "summary": "Renders an angular color-gradient background at 200 × 200 pixels.",
        "description": "### Access and colors\r\n- Public presentation only; no login, KID, permissions or database access. Does not measure equipment status.\r\n- `colors`: 2–4 colors separated by hyphens, such as `22aa88-ffcc33-ee4444`.\r\n- Colors accept 3/6-digit RGB hex without #, exact eColor names, standard named colors or transparent (case-insensitive).\r\n- Unknown colors return **400** with code `invalid-circle-parameters`; no color guessing.\r\n### Rendering and cache\r\n- Matches the legacy CircleGradient1 angular background: fills the viewport, begins at the bottom and turns clockwise.\r\n- Pure SVG; no third-party graphics components, scripts, fonts or external assets. No raster formats.\r\n- Fixed input means complete data: variants may be disk-cached for 24 hours, bounded to 512 files/32 MiB.\r\n- Cache I/O failure falls back to rendering. Public HTTP cache: 600 seconds; ETag supports **304**.\r\n- **429** means the per-process concurrency limit is reached; retry with backoff.",
        "operationId": "GetCircleGradient",
        "parameters": [
          {
            "name": "colors",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "22aa88-ffcc33-ee4444"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/circles/gradient/{colors}/{width}x{height}.svg": {
      "get": {
        "tags": [
          "Circles"
        ],
        "summary": "Renders an angular gradient at a selected width and height.",
        "description": "Same colors, access and cache as GetCircleGradient. Width/height: 16–4096 pixels each; invalid sizes return 400. The gradient fills rectangular viewports. All inputs are path segments.",
        "operationId": "GetCircleGradientSized",
        "parameters": [
          {
            "name": "colors",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "22aa88-ffcc33-ee4444"
          },
          {
            "name": "width",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 200
          },
          {
            "name": "height",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 200
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/circles/progress/{background}/{colors}/{percent}.svg": {
      "get": {
        "tags": [
          "Circles"
        ],
        "summary": "Renders a progress circle with one square mark per percentage point.",
        "description": "Public, parameter-only image at 200 × 200 pixels; same colors and cache as GetCircleGradient.\r\n`background` fills the disc; `colors` is a 2–4-color gradient over the full 0–100 scale.\r\n`percent` is an integer from 0 to 100; out-of-range values return 400, not clamped.\r\nMarks begin at the top and advance clockwise, matching CircleProgress1. Zero has no marks; 100 has all 100.\r\nThe API renders the supplied percentage; it never reads or estimates machine progress.",
        "operationId": "GetCircleProgress",
        "parameters": [
          {
            "name": "background",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "f0f4f3"
          },
          {
            "name": "colors",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "22aa88-ffcc33-ee4444"
          },
          {
            "name": "percent",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 100,
              "minimum": 0,
              "type": "integer",
              "format": "int32"
            },
            "example": 65
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/circles/progress/{background}/{colors}/{percent}/{width}x{height}.svg": {
      "get": {
        "tags": [
          "Circles"
        ],
        "summary": "Renders a progress circle at a selected width and height.",
        "description": "Same parameters and cache as GetCircleProgress. Width/height: 16–4096 pixels; the disc stays circular and centered in rectangular viewports. Invalid parameters return 400.",
        "operationId": "GetCircleProgressSized",
        "parameters": [
          {
            "name": "background",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "f0f4f3"
          },
          {
            "name": "colors",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "22aa88-ffcc33-ee4444"
          },
          {
            "name": "percent",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 100,
              "minimum": 0,
              "type": "integer",
              "format": "int32"
            },
            "example": 65
          },
          {
            "name": "width",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 200
          },
          {
            "name": "height",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 200
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/circles/running/{color}.svg": {
      "get": {
        "tags": [
          "Circles"
        ],
        "summary": "Renders a rotating semicircle to indicate running or indeterminate progress.",
        "description": "Public 200 × 200 SVG; same color validation/cache as GetCircleGradient, but one color only. Native CSS rotates once per five seconds, with no scripts or external resources. Reduced-motion preferences show a stationary semicircle. This presentation image does not query equipment status.",
        "operationId": "GetCircleRunning",
        "parameters": [
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "22aa88"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/circles/running/{color}/{width}x{height}.svg": {
      "get": {
        "tags": [
          "Circles"
        ],
        "summary": "Renders a running indicator at a selected width and height.",
        "description": "Same behavior and cache as GetCircleRunning. Width/height: 16–4096 pixels; the semicircle stays circular and centered. Invalid parameters return 400.",
        "operationId": "GetCircleRunningSized",
        "parameters": [
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "22aa88"
          },
          {
            "name": "width",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 200
          },
          {
            "name": "height",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 200
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/gradients/linear/{colors}/{angle}.svg": {
      "get": {
        "tags": [
          "Gradients"
        ],
        "summary": "Renders a linear gradient at 200 × 200 pixels.",
        "description": "Public presentation only; no login, KID, permissions or database access.\r\nColors: 2–4 hyphen-separated 3/6-digit RGB hex values without #, exact eColor names,\r\nstandard named colors or transparent (case-insensitive), evenly spaced.\r\nAngle: finite degrees, clockwise from left-to-right (0); 90 is top-to-bottom,\r\n180 right-to-left, 270 bottom-to-top. Decimal angles use a dot; negative angles and full turns normalize modulo 360.\r\nPure SVG covering the full canvas; no raster formats, scripts or external assets.\r\nInvalid parameters return 400 with invalid-gradient-parameters; malformed numbers use validation ProblemDetails.\r\nPrivate disk cache: 24 hours, up to 512 files/32 MiB; cache I/O failure falls back to rendering.\r\nPublic HTTP cache: 600 seconds; ETag/If-None-Match supports 304. On 429 retry with backoff.",
        "operationId": "GetLinearGradient",
        "parameters": [
          {
            "name": "colors",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "22aa88-ffcc33-ee4444"
          },
          {
            "name": "angle",
            "in": "path",
            "required": true,
            "schema": {
              "type": "number",
              "format": "double"
            },
            "example": 45
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/gradients/linear/{colors}/{angle}/{width}x{height}.svg": {
      "get": {
        "tags": [
          "Gradients"
        ],
        "summary": "Renders a linear gradient at a selected width and height.",
        "description": "Same colors, angle, public access, errors and cache as GetLinearGradient.\r\n            Width/height: 16–4096 pixels each. The gradient spans the full rectangle and retains its angle at the requested dimensions.\r\n            All inputs are path segments. Example: /api/v1/gradients/linear/22aa88-ffcc33/45/800x400.svg.",
        "operationId": "GetLinearGradientSized",
        "parameters": [
          {
            "name": "colors",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "22aa88-ffcc33-ee4444"
          },
          {
            "name": "angle",
            "in": "path",
            "required": true,
            "schema": {
              "type": "number",
              "format": "double"
            },
            "example": 45
          },
          {
            "name": "width",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 200
          },
          {
            "name": "height",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 200
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/icon/presentation": {
      "get": {
        "tags": [
          "Icons"
        ],
        "summary": "Resolve an IconKid, optionally changing its text, count or RGB colour.",
        "description": "Send an exact eIcon name or canonical icon KID. Optional text (up to 128 non-control characters),\r\nsigned Int64 count and colour (0–16777215) replace only those fields. Other KID fields are preserved.\r\nCalendar always uses today's day of month in Europe/Copenhagen, overriding supplied text.\r\nUse the returned iconKid unchanged in /api/v1/icon/{iconSet}/{kid}.svg. An icon-only KID is returned\r\nas its eIcon name. No login, lookup, grant or write; invalid input returns 400.\r\nRead fresh metadata when refreshing the page; the image itself retains its normal cache policy.",
        "operationId": "GetIconPresentation",
        "parameters": [
          {
            "name": "iconKid",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "text",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "count",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "color",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/IconPresentationResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IconPresentationResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/IconPresentationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/icon/catalog/{iconSet}": {
      "get": {
        "tags": [
          "Icons"
        ],
        "summary": "Lists canonical icon names with an asset in the requested local set.",
        "description": "Public; no login or database access. Returns a sorted JSON array of eIcon names.\r\nThe set is case-sensitive (for example g or line). Only direct membership is listed:\r\nunlike image rendering, this catalog does not include fallback assets from other sets.\r\nUnknown sets return 404. Clients should intersect this catalog with the allowed icons\r\nsupplied by each business operation. The catalog grants no permission to change an icon.\r\nCached for ten minutes; retry 429/503 with backoff.",
        "operationId": "GetIconAssetCatalog",
        "parameters": [
          {
            "name": "iconSet",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable"
          },
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              },
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              },
              "text/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/icon/{iconSet}/{kid}.{format}": {
      "get": {
        "tags": [
          "Icons"
        ],
        "summary": "Renders Kid.Icon from a named local asset set, with Kid.Count as the badge.",
        "description": "Public; no login, business lookup or database access. KID fields never grant permissions.\r\nSupply a URL-encoded canonical Kombine.Flex.Kid.ToString() value. Kid.Icons[0] is the main icon;\r\nKid.Icons[1] is the under-icon. A missing second entry or eIcon.none omits the under-icon.\r\nOn the trusted Nortec site, the line-set check under-icon uses Nortec green (#4AC337).\r\nAdditional entries are ignored for rendering. The API also reads Kid.Count, Kid.Color and Kid.Text.\r\nIf KID parsing fails, an exact eIcon name (case-insensitive) uses count zero, black and empty text.\r\nNumeric enum IDs, substring matches, malformed KIDs and undefined icons are not name fallbacks.\r\nCount zero hides the badge. Nonzero Int64 counts display their full absolute value, including Int64.MinValue.\r\nPositive counts use white with black digits; negative counts use red with white digits. The badge widens\r\nwith circular end caps and a straight middle, keeping its height and font size. Long labels widen the SVG canvas.\r\nChoose iconSet explicitly: line or g. The g set preserves its original multicolor artwork.\r\nMain and under-icon each try the chosen set, then other packaged sets in ordinal alphabetical order.\r\nUnknown sets or icons absent from every local set return 404; no external or database fallback.\r\nKid.Color uses the low 24 bits as opaque RGB (0 = black; the high byte is ignored).\r\nKid.Text is case-sensitive, limited to 128 characters without controls and used by assets with a text box.\r\nURL-encode path parameters. The canonical KID\r\nis limited to 2048 characters; other path parameters to 128 characters without controls.\r\nFormats: svg, png, jpg/jpeg, gif, bmp, tif/tiff, webp, ppm, tga and ico. Unknown formats return SVG.\r\nRaster output defaults to 128 × 128 pixels; SVG retains its authored viewport.\r\nFirst requests render to disk; repeated requests use the cache. Public cache: ten minutes.\r\nETag/Last-Modified support 304. Invalid parameters return 400; concurrency limits return 429;\r\nunavailable rendering/storage returns 503. Retry transient errors with backoff.\r\nRoutes without iconSet and the separate count/color/text/sub path segments have been removed; see /docs#changelog.",
        "operationId": "GetIconFromSet",
        "parameters": [
          {
            "name": "iconSet",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "line"
          },
          {
            "name": "kid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "413132x7qE20i11Bi336699Ic"
          },
          {
            "name": "format",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "svg"
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable"
          },
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/gif": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/bmp": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/tiff": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/webp": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/x-icon": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/x-portable-pixmap": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/x-tga": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          }
        }
      }
    },
    "/api/v1/icon/{iconSet}/{kid}/{size}.{format}": {
      "get": {
        "tags": [
          "Icons"
        ],
        "summary": "Renders Kid.Icon and Kid.Count from a named set at a square pixel size.",
        "description": "Same KID/name fallback, access, asset lookup, formats and cache as GetIconFromSet.\r\nSize is clamped to 16–4096 pixels (ICO at most 256). SVG retains its authored viewport.",
        "operationId": "GetIconImageFromSet",
        "parameters": [
          {
            "name": "iconSet",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "line"
          },
          {
            "name": "kid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "413132x7qE20i11Bi336699Ic"
          },
          {
            "name": "size",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int32"
            },
            "example": 128
          },
          {
            "name": "format",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "svg"
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable"
          },
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/gif": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/bmp": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/tiff": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/webp": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/x-icon": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/x-portable-pixmap": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/x-tga": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/icon/{iconSet}/{kid}/{backColor}/{size}.{format}": {
      "get": {
        "tags": [
          "Icons"
        ],
        "summary": "Renders Kid.Icon and Kid.Count from a named set with a raster background.",
        "description": "Same KID/name fallback, access, asset lookup, formats and cache as GetIconFromSet.\r\nSame size limits as GetIconImageFromSet. Background accepts Flex/known color names or 3/6-digit\r\nRGB hex. Omit the background segment for transparent raster output. SVG ignores size/background.",
        "operationId": "GetIconImageWithBackgroundFromSet",
        "parameters": [
          {
            "name": "iconSet",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "line"
          },
          {
            "name": "kid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "413132x7qE20i11Bi336699Ic"
          },
          {
            "name": "backColor",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "white"
          },
          {
            "name": "size",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int32"
            },
            "example": 128
          },
          {
            "name": "format",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "svg"
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable"
          },
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/gif": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/bmp": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/tiff": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/webp": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/x-icon": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/x-portable-pixmap": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/x-tga": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/logos/kombine/{color}.svg": {
      "get": {
        "tags": [
          "Logos"
        ],
        "summary": "Renders a responsive Kombine symbol that fits its viewport without stretching.",
        "description": "### Local artwork and access\r\n- Public; no login, business KID, Tab, permission or database access.\r\n- Original KombineLogo1 geometry, serialized with .NET XML; no graphics packages or external resources.\r\n- The unsized SVG uses only a viewBox and `preserveAspectRatio=\"xMidYMid meet\"`: it fills the available viewport as far as possible without cropping or stretching. The background is transparent.\r\n- No fixed width/height attributes are emitted. Use the width-specific route or set dimensions on the embedding element when a fixed pixel size is needed.\r\n- `color` accepts 3/6-digit RGB hex without #, exact eColor names, standard color names or transparent, case-insensitively.\r\n- Unknown colors return **400** with code `invalid-logo-parameters`; no substring or index guesses.\r\n- Select individual parts with `/kombine/{parts}/{color}.svg`: 1–4 letters N/E/S/W, case-insensitive (for example `/kombine/W/black.svg` or `/kombine/WE/black.svg`). These standard routes return the complete symbol. N/S select the vertical bars; W/E each select a horizontal bar and curved path. The original square viewBox and positions are preserved. Invalid selections return 400 with code `invalid-logo-parameters`.\r\n### Format and cache\r\n- SVG only. All parameters are path segments, and the URL ends in .svg. No raster fallback.\r\n- First request renders to a private disk cache; subsequent requests reuse it. All logo inputs are fixed, complete presentation data.\r\n- Maximum 512 variants/32 MiB, 24-hour lifetime. Cache I/O failure falls back to fresh rendering.\r\n- Public browser cache: 600 seconds; ETag/If-None-Match supports **304**.\r\n- **429** means the limit of 16 simultaneous logo requests was reached; retry with backoff.",
        "operationId": "GetKombineLogo",
        "parameters": [
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "white"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/logos/kombine/{color}/{width}.svg": {
      "get": {
        "tags": [
          "Logos"
        ],
        "summary": "Renders the square Kombine symbol at the selected width.",
        "description": "Same colors, access and cache as GetKombineLogo. Width: integer 16–4096 pixels; height equals width. Invalid width returns 400 instead of being clamped.",
        "operationId": "GetKombineLogoSized",
        "parameters": [
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "white"
          },
          {
            "name": "width",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 512
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/logos/kombine/{color}/{background}/{width}.svg": {
      "get": {
        "tags": [
          "Logos"
        ],
        "summary": "Renders the Kombine symbol with an explicit background and width.",
        "description": "Same rules as GetKombineLogoSized. Background accepts the same colors as the foreground and is painted into the SVG. Use transparent to omit it.",
        "operationId": "GetKombineLogoWithBackground",
        "parameters": [
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "white"
          },
          {
            "name": "background",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "174d61"
          },
          {
            "name": "width",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 512
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/logos/kombine/{parts}/{color}.svg": {
      "get": {
        "tags": [
          "Logos"
        ],
        "summary": "Renders selected parts of the Kombine symbol in its original square viewport.",
        "description": "Parts: 1–4 letters N/E/S/W, case-insensitive; combinations such as WE are supported. N/S select vertical bars; W/E each select a horizontal bar and curved path. Example: /api/v1/logos/kombine/W/black.svg. All parameters are path segments. Same colors, public access and cache as GetKombineLogo. Invalid parts return 400 with code invalid-logo-parameters.",
        "operationId": "GetKombineLogoParts",
        "parameters": [
          {
            "name": "parts",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^[nNeEsSwW]+$",
              "type": "string"
            },
            "example": "W"
          },
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "white"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/logos/kombine/{parts}/{color}/{width}.svg": {
      "get": {
        "tags": [
          "Logos"
        ],
        "summary": "Renders selected Kombine symbol parts at the selected width.",
        "description": "Same parts, colors, public access and cache as GetKombineLogoParts. Width: integer 16–4096 pixels; height equals width. Invalid width returns 400. Example: /api/v1/logos/kombine/WE/black/128.svg.",
        "operationId": "GetKombineLogoPartsSized",
        "parameters": [
          {
            "name": "parts",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^[nNeEsSwW]+$",
              "type": "string"
            },
            "example": "W"
          },
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "white"
          },
          {
            "name": "width",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 512
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/logos/kombine/{parts}/{color}/{background}/{width}.svg": {
      "get": {
        "tags": [
          "Logos"
        ],
        "summary": "Renders selected Kombine symbol parts with an explicit background and width.",
        "description": "Same rules as GetKombineLogoPartsSized. Background accepts the same colors as the foreground; transparent omits it. Example: /api/v1/logos/kombine/WE/white/174d61/128.svg.",
        "operationId": "GetKombineLogoPartsWithBackground",
        "parameters": [
          {
            "name": "parts",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^[nNeEsSwW]+$",
              "type": "string"
            },
            "example": "W"
          },
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "white"
          },
          {
            "name": "background",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "174d61"
          },
          {
            "name": "width",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 512
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/logos/kombine-text/{color}.svg": {
      "get": {
        "tags": [
          "Logos"
        ],
        "summary": "Renders the Kombine name and registered mark, without the symbol.",
        "description": "Original KombineText1 outlines are embedded locally; no installed or downloaded font is needed. No fixed width/height: the wordmark fits its viewport with the original 590:111 aspect ratio, centered without cropping or stretching. Transparent background. Same colors, public access, SVG-only format and cache as GetKombineLogo.",
        "operationId": "GetKombineText",
        "parameters": [
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "white"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/logos/kombine-text/{color}/{width}.svg": {
      "get": {
        "tags": [
          "Logos"
        ],
        "summary": "Renders the Kombine wordmark at the selected width.",
        "description": "Same rules as GetKombineText. Width: integer 16–4096 pixels; height is width × 111/590, preserving the artwork. Invalid width returns 400. Transparent background.",
        "operationId": "GetKombineTextSized",
        "parameters": [
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "white"
          },
          {
            "name": "width",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 512
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/logos/kombine-text/{color}/{background}/{width}.svg": {
      "get": {
        "tags": [
          "Logos"
        ],
        "summary": "Renders the Kombine wordmark with an explicit background and width.",
        "description": "Same rules as GetKombineTextSized. Background accepts the same colors as the foreground and fills the SVG viewBox; transparent omits it.",
        "operationId": "GetKombineTextWithBackground",
        "parameters": [
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "white"
          },
          {
            "name": "background",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "174d61"
          },
          {
            "name": "width",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 512
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/logos/kombine-logo-text/{color}.svg": {
      "get": {
        "tags": [
          "Logos"
        ],
        "summary": "Renders the Kombine symbol and name together.",
        "description": "Original KombineLogoText1 composition, spacing and proportions. No fixed width/height: the logo fits its viewport with a 5:1 aspect ratio, centered without cropping or stretching; transparent background. Local path outlines, no fonts. Same color validation, public access, SVG-only format and cache as GetKombineLogo.",
        "operationId": "GetKombineLogoText",
        "parameters": [
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "white"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/logos/kombine-logo-text/{color}/{width}.svg": {
      "get": {
        "tags": [
          "Logos"
        ],
        "summary": "Renders the combined Kombine logo at the selected width.",
        "description": "Same rules as GetKombineLogoText. Width: integer 16–4096 pixels; height is width/5. The artwork is never stretched. Invalid width returns 400. Transparent background.",
        "operationId": "GetKombineLogoTextSized",
        "parameters": [
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "white"
          },
          {
            "name": "width",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 512
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/logos/kombine-logo-text/{color}/{background}/{width}.svg": {
      "get": {
        "tags": [
          "Logos"
        ],
        "summary": "Renders the combined Kombine logo with an explicit background and width.",
        "description": "Same rules as GetKombineLogoTextSized. Background accepts the same colors as the foreground and fills the SVG viewBox; transparent omits it. Example: /api/v1/logos/kombine-logo-text/white/174d61/512.svg.",
        "operationId": "GetKombineLogoTextWithBackground",
        "parameters": [
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "white"
          },
          {
            "name": "background",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "174d61"
          },
          {
            "name": "width",
            "in": "path",
            "required": true,
            "schema": {
              "maximum": 4096,
              "minimum": 16,
              "type": "integer",
              "format": "int32"
            },
            "example": 512
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/statistics/live-machines": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Returns the site overview's running-machine pool, refreshed at most once a minute.",
        "description": "Anonymous on each tenant site; data is restricted to the trusted configured tenant. No parameters. Current Log24 Started < now < Done, in UTC milliseconds since 2000. Names are eSetting.Name, temperature is optional eState.Temperature. Conflicting/missing lifecycle fields are excluded. Random sample of up to 1000 machines started within 100 hours and ending within 3 hours, from at most 100000 source rows; source overflow or unavailable storage returns 503. Clients choose distinct random machines, retain running selections and count down locally using end time. Only currently running machines with a valid end time after now and no more than 3 hours ahead are eligible; stored Done values outside this window are excluded. The response schema still allows null for DoneAtUtc; clients should not select entries without a valid end time in this window. Up to 10000 metadata rows are read for the running-machine scope. Refresh every 60 seconds.",
        "operationId": "GetPublicLiveMachines",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LiveMachineResponse"
                  }
                }
              },
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LiveMachineResponse"
                  }
                }
              },
              "text/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LiveMachineResponse"
                  }
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable"
          }
        }
      }
    },
    "/api/v1/public/widgets/machine-errors": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "List machines with a positive AutoOutOfOrder Id without login.",
        "description": "Explicit public read-only widget. Tenant is trusted site configuration, never request input.\r\n            Reads Log24 only; valid AutoOutOfOrder JSON with positive integer Id is required.\r\n            The state's MS2000 event time must be within the last 10 days, inclusive; unknown/future times are excluded.\r\n            Excludes deleted banks, locations and units and requires an enabled location. Disabled units remain included.\r\n            Publishes only unit KID, localized machine/bank/location names and errorId as a decimal digit string.\r\n            No raw error JSON, credentials, manager settings, coordinates or hierarchy are returned.\r\n            Pages contain 1–100 items (default 50), ordered by numeric bank/location/unit identity.\r\n            Follow nextCursor with unchanged pageSize; cursors are site-bound, protected and expire after 15 minutes.\r\n            Pages are cached for 60 seconds with bounded concurrency; refresh from the first page.\r\n            400 invalid page/cursor; 503 unavailable with Retry-After 60. An error is never an empty success.",
        "operationId": "GetPublicMachineErrors",
        "parameters": [
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PublicMachineErrorsPage"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicMachineErrorsPage"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicMachineErrorsPage"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/camera/photos": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Get the ten newest photos, including Live Photo stills, newest first.",
        "description": "No login or tenant parameter. Includes existing captures from both regional indexes, deduplicated. Cached for 60 seconds. Only thumbnails are public; original media, owner, filenames and sensor metadata remain protected. Workspace paths require normal manager Camera1 and resource Read permissions when opened. Returns 503 if a complete bounded snapshot cannot be established (10,000 index entries or 200 candidate manifests maximum). Videos are excluded.",
        "operationId": "GetPublicRecentCameraPhotos",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PublicCameraPhoto"
                  }
                }
              },
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PublicCameraPhoto"
                  }
                }
              },
              "text/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PublicCameraPhoto"
                  }
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/camera/photos/{id}/thumbnail": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Read only the JPEG thumbnail authorized by a ten-minute, tenant-bound public ticket.",
        "description": "Tickets from GetPublicRecentCameraPhotos cannot read original files or private camera operations. No arbitrary path or part is accepted. Invalid, expired and cross-tenant tickets return 400, missing thumbnails 404, unavailable storage 503.",
        "operationId": "GetPublicCameraThumbnail",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "text/json": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable"
          }
        }
      }
    },
    "/api/v1/public/statistics/purchases/chart/{color}.svg": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Draws purchase counts for the last hour in twelve five-minute intervals.",
        "description": "### Scope and time window\r\n- **No login required.** Uses the configured site's tenant, without caller-selected tenants, dates or filters.\r\n- Uses the same base Log1Hour filters and shared one-minute measurement as GetPublicPurchases:\r\n  eUserId.Users through eUserId.Guest, inclusive, Text NOT LIKE '%E', and Amount < 0.\r\n- Counts rows across currencies, without summing or converting money. No personal transaction data is returned.\r\n- Twelve consecutive intervals cover [sinceUtc, measuredAtUtc), oldest first; each interval includes its start and excludes its end.\r\n- Only the chart applies this exact MS2000 time window. GetPublicPurchases retains the total of all qualifying rows in Log1Hour,\r\n  so chart counts can sum to less than that total when database cleanup is delayed or rows have future timestamps.\r\n- The twelve interval values are spaced evenly from the left edge to the right edge, oldest first.\r\n- The vertical range follows the observed minimum and maximum; a constant series, including all zeroes, is centered vertically.\r\n### Image and color\r\n- Transparent sparkline with a fixed 320 × 80 viewBox, displayed across the card's full inner width.\r\n- Only a line, without area fill, visible numbers, axis labels or point markers; no scripts, animation, external fonts or assets.\r\n- The final segment ends in an open arrowhead aligned with that segment; each wing is half the segment's length, with the same stroke and color and no fill.\r\n- `color`: 3/6-digit RGB hex without #, an exact eColor name, a standard named color, or transparent (case-insensitive).\r\n- Empty data draws a centered flat line. The SVG description retains exact counts for accessibility.\r\n- The root svg element's `data-counts` attribute contains the same twelve counts as the line and description:\r\n  comma-separated, nonnegative integers in invariant format without grouping separators, oldest first.\r\n- For img rendering, use those values in localized alt text and reuse the fetched SVG for the image; no extra data request is needed.\r\n- HTTP responses use **no-store**; the underlying measurement is shared with JSON requests for one minute per API instance.\r\n### Errors\r\n- **400**, code `invalid-purchase-chart-color`: use a supported color. Validation occurs before database access.\r\n- **503**, code `purchase-statistics-unavailable`: no image is returned; wait **60 seconds** before retrying.",
        "operationId": "GetPublicPurchasesChart",
        "parameters": [
          {
            "name": "color",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/displays/Map1": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Shows recent purchases as coin markers on a map. No login required.",
        "description": "### Public scope and selection\r\n            \r\n- **No login required.** No tenant, bank or time-window override is supported.\r\n- Returns a newest-first sample of purchases retained in **Log1Hour**, without a timestamp filter.\r\n- `limit`: **1–200**, default **100**.\r\n- `heatmap` contains all retained qualifying purchases grouped by latitude/longitude, with summed major-unit `amount`.\r\n  It is independent of `limit` and displayed immediately, not on coin landing. Both arrays refresh together every ten seconds.\r\n- The database maintains Log1Hour as the recent-hour table; no additional timestamp filter or row limit is applied to the heatmap.\r\n- This is a **visual sample**, not accounting data; missing coordinates can omit purchases.\r\n            \r\n### Amounts and identifiers\r\n            \r\n- Amounts are positive major currency units; currencies are not converted.\r\n- Transfers and rows whose Text ends in `E` are excluded.\r\n- `timestampUtc` is the original MS2000 converted directly to UTC.\r\n- `kid` is a canonical transaction KID with the original timestamp. Use it to suppress duplicate animations.\r\n- KIDs identify the tenant/bank/location/unit event, grant no permissions and contain no user ID.\r\n- No names, tags or transaction text are returned.\r\n            \r\n### Refresh and errors\r\n            \r\n- One shared **10-second cache** per API instance. `refreshAfterSeconds` is always **10** on success.\r\n- Failures use a **30-second backoff**. On **HTTP 503**, retry after **30 seconds**.",
        "operationId": "GetPublicDisp73",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "maximum": 200,
              "minimum": 1,
              "type": "integer",
              "format": "int32",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseMapSnapshot"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseMapSnapshot"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseMapSnapshot"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable"
          }
        }
      }
    },
    "/api/v1/public/widgets/progress/{kid}": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Get progress for one unit or a page of all public units in a location or bank.",
        "description": "Anonymous by explicit widget publication policy. Supply a canonical Unit, Location or Bank KID belonging to this API site's configured tenant. Reads Log24 only; no Alive, Orders, account data or manager permissions. Public visibility excludes deleted banks/locations/units and requires an enabled location; disabled units remain included. Location and bank pages include child units and idle/unknown timing, not a random running-machine sample. Each page contains at most 100 units in numeric bank/location/unit order. Follow nextCursor until null; cursors bind site, exact scope and page size, expire after 15 minutes and grant no private access. Refresh loaded pages from the first page every 60 seconds, using fresh cursors; pages are not a frozen snapshot. Names are localized using Accept-Language. Progress uses the same Started/Done calculation as the portal; missing/invalid timing is unknown, never invented zero. No temperature or additional settings are published. JS: mountProgressWidgets from /docs/widgets.mjs; examples: /docs/widgets. 400 invalid scope/page/cursor, 404 no public unit, 503 unavailable/busy with Retry-After: 60; failures are never returned as empty success.",
        "operationId": "GetPublicScopedUnitProgress",
        "parameters": [
          {
            "name": "kid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PublicScopedProgressResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicScopedProgressResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicScopedProgressResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/statistics/purchases": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Counts purchases in the site's Log1Hour without login.",
        "description": "### Public scope and selection\r\n            \r\n- **No login required.** The site selects the tenant; no tenant or filter override is accepted.\r\n- Uses `eUserId.Users` through `eUserId.Guest`, inclusive, `Text NOT LIKE '%E'` and `Amount < 0`.\r\n- Counts **rows**, not distinct users.\r\n            \r\n### Time window and amounts\r\n            \r\n- **Log1Hour** holds recent transactions and is cleaned every minute by the database.\r\n- The one-hour window depends on that maintenance. `sinceUtc` is the nominal start at measurement time.\r\n- The total count and `currencies` are cached for **one minute** per API instance.\r\n- The SVG chart shares this snapshot but applies an exact timestamp window; its interval counts can differ from this total when rows outside that window remain in Log1Hour.\r\n- Each currency entry contains `currency`, its purchase `count` and `amount` (`-SUM(Amount)/100` for that currency).\r\n- Amounts remain separate per stored currency; no currency conversion is performed.\r\n- No purchases returns `count: 0` and `currencies: []`; a null currency code remains a separate entry.\r\n            \r\n### Errors\r\n            \r\n- **HTTP 503:** wait **60 seconds** before retrying.",
        "operationId": "GetPublicPurchases",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PurchasesResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchasesResponse"
                },
                "example": {
                  "tenantKid": "3E7QAo",
                  "count": 42,
                  "currencies": [
                    {
                      "currency": "DKK",
                      "count": 30,
                      "amount": 100.25
                    },
                    {
                      "currency": "EUR",
                      "count": 12,
                      "amount": 23.20
                    }
                  ],
                  "lookbackHours": 1,
                  "sinceUtc": "2026-09-23T09:00:00+00:00",
                  "measuredAtUtc": "2026-09-23T10:00:00+00:00"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchasesResponse"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/statistics/active-users": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Counts active users in the site's current Log7 during the last 100 days, without login.",
        "description": "### Public scope and count\r\n            \r\n- **No login required.** The site chooses the tenant; no caller-supplied KID, dates or filters are accepted.\r\n- Counts distinct **(BankId, UserId)** pairs with **BankId ≥ 1000** and either UserId **1001–99999** (`eUserId.UsersLast` inclusive) or **999999999** (`eUserId.Guest`).\r\n- Activity means a current **Log7** record's `MS2000` exceeds the UTC **100-day cutoff**; it is not a verified login.\r\n- No personal data is returned.\r\n            \r\n### Cache and errors\r\n            \r\n- Results are cached for **5 minutes** per API instance. Concurrent requests share one query.\r\n- A failed refresh returns **HTTP 503**, not zero. Wait **60 seconds** before retrying.",
        "operationId": "GetPublicActiveUsers",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ActiveUsersResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActiveUsersResponse"
                },
                "example": {
                  "tenantKid": "3E7QAo",
                  "count": 12450,
                  "lookbackDays": 100,
                  "sinceUtc": "2026-06-15T10:00:00+00:00",
                  "measuredAtUtc": "2026-09-23T10:00:00+00:00"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActiveUsersResponse"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/widgets/washdoc": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Get a square WashDoc widget's graph, machine/location names and document times.",
        "description": "Anonymous publication explicitly requested by the owner. documentKid and unitKid are optional and mutually exclusive.\r\nA canonical document KID selects that document. A unit KID follows its current stored WashDocId, including its last completed document.\r\nWith neither, select the most recently started currently running public unit in this tenant. No random selection.\r\nPublic visibility requires an enabled location and non-deleted bank/location/unit; it is rechecked through a bounded 60-second snapshot.\r\nOnly the simplified graph, canonical document/unit KIDs, names, times and finished status are published; no raw tables, residents, payments or credentials.\r\nUses the same WashDoc data acquisition, legacy numeric decoding, cycle window and type-specific rendering profile as the full graph.\r\nThe original UnitType selects measurement emphasis; Alliance dryers 19/29/30 interpret legacy Level as Temperature2, not water.\r\nFixed measurement/event inputs and Cycle only; no caller-selected fields. Finished graphs use the bounded private graph cache.\r\nRefresh visible widgets every 60 seconds. HTTP no-store and anonymous GET-only CORS. 400 invalid/foreign KID; 404 no public unit/document;\r\n422 no numeric graph/oversized data; 503 busy/unavailable with Retry-After: 60. Full documents and downloads remain authenticated.\r\nImplemented locally, unreleased.",
        "operationId": "GetPublicWashDocWidget",
        "parameters": [
          {
            "name": "documentKid",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "unitKid",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PublicWashDocWidgetResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicWashDocWidgetResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicWashDocWidgetResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/widgets": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Get localized labels and the site's palette for the shared JavaScript widgets.",
        "description": "No login. Tenant comes exclusively from trusted site configuration. Accept-Language selects a supported language, with Danish fallback for missing translations. Washco uses English. This operation reads no database. Load /docs/widgets.mjs and call mountWidgets; see /docs/widgets for live examples. Business data comes from the existing public statistics operations and retains their bounds, caches, error responses and anonymous read-only CORS policy.",
        "operationId": "GetPublicWidgetPresentation",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PublicWidgetPresentationResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicWidgetPresentationResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicWidgetPresentationResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/statistics/reservations": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Counts noncancelled dated reservations that have not ended, including ongoing reservations.",
        "description": "Anonymous tenant aggregate; trusted site configuration selects the tenant without request inputs.\r\nUses the current tenant Log5 snapshot keyed by start/bank/location/unit/type. Amount must be positive.\r\nEnd is local start minutes plus duration; location/bank Log24 time-zone settings are respected.\r\nWeekly templates and AUT/isy auxiliary entries are excluded. Invalid or ambiguous time-zone settings fail closed.\r\nNo resident or reservation identities are returned. Results and failures are cached for 60 seconds.\r\nMore than 100000 candidate rows, invalid time zones or unavailable storage return 503; retry after 60 seconds.",
        "operationId": "GetPublicReservations",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ReservationsStatisticsResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReservationsStatisticsResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReservationsStatisticsResponse"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable"
          }
        }
      }
    },
    "/api/v1/public/statistics/running-machines": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Counts machines whose current Started is before now and Done is after now.",
        "description": "No login required. The trusted site selects the tenant; no tenant, KID or filter inputs.\r\nReads only Log24 States for Started and Done. Text values are UTC milliseconds since 2000-01-01,\r\naccepting stored JSON strings or plain integers. Counts distinct BankId/LocationId/UnitId identities.\r\nLatest MS2000 revision wins per field; conflicting equal revisions, missing or invalid values do not qualify.\r\nStarted must be within the last 100 hours (inclusive), strictly before now, and Done strictly after now; this is a time-window estimate, not an online-status check.\r\nResults and failures are shared for 15 seconds per API instance. Poll every 15 seconds.\r\nA bounded read exceeding 100000 lifecycle rows or a storage failure returns 503, never a partial count or zero.\r\nNo machine identities or access permissions are returned. On 503 retry after 15 seconds.",
        "operationId": "GetPublicRunningMachines",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/RunningMachinesResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunningMachinesResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunningMachinesResponse"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable"
          }
        }
      }
    },
    "/api/v1/status": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Gets public API availability. This does not check database readiness.",
        "description": "### Availability\r\n            \r\n- **Public; no login required.**\r\n- Returns the service name, availability and contract version.\r\n- Checks API availability only; it **does not check database readiness**.",
        "operationId": "GetPortalStatus",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiStatusResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiStatusResponse"
                },
                "example": {
                  "service": "Kombine Flex Portal API",
                  "status": "Available",
                  "apiVersion": "v1"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiStatusResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ActiveUsersResponse": {
        "type": "object",
        "properties": {
          "tenantKid": {
            "type": "string",
            "description": "The configured site's tenant KID.",
            "nullable": true
          },
          "count": {
            "type": "integer",
            "description": "Distinct qualifying bank/user pairs, including a genuine zero when no pairs match.",
            "format": "int64"
          },
          "lookbackDays": {
            "type": "integer",
            "description": "The fixed 100-day lookback.",
            "format": "int32"
          },
          "sinceUtc": {
            "type": "string",
            "description": "Exclusive UTC lower bound used for Log7.MS2000.",
            "format": "date-time"
          },
          "measuredAtUtc": {
            "type": "string",
            "description": "UTC time used for this cached count; it may be up to 5 minutes old.",
            "format": "date-time"
          }
        },
        "additionalProperties": false,
        "description": "Public tenant-wide count; does not expose or grant access to any individual account."
      },
      "ApiStatusResponse": {
        "type": "object",
        "properties": {
          "service": {
            "type": "string",
            "description": "The public service name.",
            "nullable": true
          },
          "status": {
            "type": "string",
            "description": "The service availability.",
            "nullable": true
          },
          "apiVersion": {
            "type": "string",
            "description": "The API contract version.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Describes public service availability without exposing database or customer data."
      },
      "IconPresentationResponse": {
        "type": "object",
        "properties": {
          "iconKid": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Opaque image identity: eIcon name for an icon alone, otherwise canonical Kid.ToString()."
      },
      "LiveMachineResponse": {
        "type": "object",
        "properties": {
          "unitKid": {
            "type": "string",
            "nullable": true
          },
          "unitName": {
            "type": "string",
            "nullable": true
          },
          "locationName": {
            "type": "string",
            "nullable": true
          },
          "startedAtUtc": {
            "type": "string",
            "format": "date-time"
          },
          "doneAtUtc": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "temperature": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Minimal public site-tenant machine display, without resident or payment fields."
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "instance": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": { }
      },
      "PublicCameraPhoto": {
        "type": "object",
        "properties": {
          "thumbnailUrl": {
            "type": "string",
            "nullable": true
          },
          "workspacePath": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Public thumbnail URL and relative portal destination; no original or sensor metadata."
      },
      "PublicMachineError": {
        "type": "object",
        "properties": {
          "kid": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "bankName": {
            "type": "string",
            "nullable": true
          },
          "locationName": {
            "type": "string",
            "nullable": true
          },
          "errorId": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Public identity, display names and error code; no raw state payload."
      },
      "PublicMachineErrorsPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicMachineError"
            },
            "nullable": true
          },
          "nextCursor": {
            "type": "string",
            "nullable": true
          },
          "refreshAfterSeconds": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false,
        "description": "Public machine-error page, with a protected next-page cursor and refresh interval."
      },
      "PublicScopedProgressItem": {
        "type": "object",
        "properties": {
          "unitKid": {
            "type": "string",
            "description": "Canonical site-bound unit identity.",
            "nullable": true
          },
          "unitName": {
            "type": "string",
            "description": "Localized unit name.",
            "nullable": true
          },
          "locationName": {
            "type": "string",
            "description": "Localized location name.",
            "nullable": true
          },
          "startedAtUtc": {
            "type": "string",
            "description": "Expected start or null for unknown timing.",
            "format": "date-time",
            "nullable": true
          },
          "doneAtUtc": {
            "type": "string",
            "description": "Expected end or null for unknown timing.",
            "format": "date-time",
            "nullable": true
          },
          "progress": {
            "$ref": "#/components/schemas/UnitProgressResponse"
          }
        },
        "additionalProperties": false,
        "description": "Minimal public unit display and expected timing."
      },
      "PublicScopedProgressResponse": {
        "type": "object",
        "properties": {
          "scopeKid": {
            "type": "string",
            "description": "Requested canonical KID.",
            "nullable": true
          },
          "scopeKind": {
            "type": "string",
            "description": "Unit, Location or Bank enum name.",
            "nullable": true
          },
          "calculatedAtUtc": {
            "type": "string",
            "description": "UTC calculation time.",
            "format": "date-time"
          },
          "refreshAfterSeconds": {
            "type": "integer",
            "description": "Recommended refresh interval.",
            "format": "int32"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicScopedProgressItem"
            },
            "description": "Public units in numeric identity order.",
            "nullable": true
          },
          "nextCursor": {
            "type": "string",
            "description": "Opaque next-page cursor, or null after the last page.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "A bounded page of public progress for the exact site-bound scope."
      },
      "PublicWashDocWidgetResponse": {
        "type": "object",
        "properties": {
          "documentKid": {
            "type": "string",
            "nullable": true
          },
          "unitKid": {
            "type": "string",
            "nullable": true
          },
          "unitName": {
            "type": "string",
            "nullable": true
          },
          "locationName": {
            "type": "string",
            "nullable": true
          },
          "startedAtUtc": {
            "type": "string",
            "format": "date-time"
          },
          "endedAtUtc": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "finished": {
            "type": "boolean"
          },
          "graphSvg": {
            "type": "string",
            "nullable": true
          },
          "refreshAfterSeconds": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false,
        "description": "Explicitly public, minimal WashDoc presentation. GraphSvg is text-free first-party SVG, not a raw measurement table."
      },
      "PublicWidgetPresentationResponse": {
        "type": "object",
        "properties": {
          "language": {
            "type": "string",
            "description": "Resolved supported culture for labels, names and number formatting.",
            "nullable": true
          },
          "colors": {
            "type": "string",
            "description": "Hyphen-separated RGB hex palette for the API's circle SVGs.",
            "nullable": true
          },
          "background": {
            "type": "string",
            "description": "RGB hex background inside the progress circle.",
            "nullable": true
          },
          "webBase": {
            "type": "string",
            "description": "Trusted HTTPS portal origin for the public purchase map, or null when unavailable.",
            "nullable": true
          },
          "labels": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Existing localized portal text keys used by the widgets; contains no account data.",
            "nullable": true
          },
          "googleMapsBrowserKey": {
            "type": "string",
            "description": "Public browser Maps key, restricted by HTTP referrer at Google; never a server credential. Null when maps are not configured.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Public presentation selected by this API site's trusted tenant."
      },
      "PurchaseCurrencySnapshot": {
        "type": "object",
        "properties": {
          "currency": {
            "type": "string",
            "description": "Stored currency code; null represents purchases with no currency code.",
            "nullable": true
          },
          "count": {
            "type": "integer",
            "description": "Number of matching purchase rows for this currency.",
            "format": "int64"
          },
          "amount": {
            "type": "number",
            "description": "Positive total in major currency units (negated stored Amount divided by 100).",
            "format": "double"
          }
        },
        "additionalProperties": false,
        "description": "Purchase count and total for one currency; no currency conversion is performed."
      },
      "PurchaseHeatmapPoint": {
        "type": "object",
        "properties": {
          "latitude": {
            "type": "number",
            "format": "double"
          },
          "longitude": {
            "type": "number",
            "format": "double"
          },
          "amount": {
            "type": "number",
            "format": "double"
          }
        },
        "additionalProperties": false,
        "description": "Total revenue at a coordinate across all retained purchases, independent of the coin limit."
      },
      "PurchaseMapPoint": {
        "type": "object",
        "properties": {
          "kid": {
            "type": "string",
            "nullable": true
          },
          "latitude": {
            "type": "number",
            "format": "double"
          },
          "longitude": {
            "type": "number",
            "format": "double"
          },
          "timestampUtc": {
            "type": "string",
            "format": "date-time"
          },
          "amount": {
            "type": "number",
            "format": "double"
          }
        },
        "additionalProperties": false,
        "description": "Canonical transaction KID and UTC display time (original MS2000 without an offset). Amount is positive major units; currencies are not converted."
      },
      "PurchaseMapSnapshot": {
        "type": "object",
        "properties": {
          "measuredAtUtc": {
            "type": "string",
            "format": "date-time"
          },
          "refreshAfterSeconds": {
            "type": "integer",
            "format": "int32"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PurchaseMapPoint"
            },
            "nullable": true
          },
          "heatmap": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PurchaseHeatmapPoint"
            },
            "description": "Complete retained purchase revenue grouped by coordinates; never limited by the animated coin count.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "A bounded, public display sample, not a complete transaction ledger."
      },
      "PurchasesResponse": {
        "type": "object",
        "properties": {
          "tenantKid": {
            "type": "string",
            "description": "Configured site tenant.",
            "nullable": true
          },
          "count": {
            "type": "integer",
            "description": "Number of matching purchase rows.",
            "format": "int64"
          },
          "lookbackHours": {
            "type": "integer",
            "description": "Nominal one-hour window maintained by the database.",
            "format": "int32"
          },
          "sinceUtc": {
            "type": "string",
            "description": "Nominal window start; actual rows depend on Log1Hour cleanup.",
            "format": "date-time"
          },
          "measuredAtUtc": {
            "type": "string",
            "description": "UTC measurement time.",
            "format": "date-time"
          },
          "currencies": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PurchaseCurrencySnapshot"
            },
            "description": "One count and amount per stored currency; empty for no matches. No currency conversion.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Public purchase aggregate in Log1Hour, cached for up to one minute."
      },
      "ReservationsStatisticsResponse": {
        "type": "object",
        "properties": {
          "tenantKid": {
            "type": "string",
            "description": "Configured site's canonical tenant KID.",
            "nullable": true
          },
          "count": {
            "type": "integer",
            "description": "Qualifying reservations; zero only for a successful empty count.",
            "format": "int64"
          },
          "measuredAtUtc": {
            "type": "string",
            "description": "UTC instant used for the strict reservation-end comparison.",
            "format": "date-time"
          },
          "refreshAfterSeconds": {
            "type": "integer",
            "description": "Recommended polling interval, 60 seconds.",
            "format": "int32"
          }
        },
        "additionalProperties": false,
        "description": "Public count of reservations that have not ended."
      },
      "RunningMachinesResponse": {
        "type": "object",
        "properties": {
          "tenantKid": {
            "type": "string",
            "description": "Configured site's canonical tenant KID.",
            "nullable": true
          },
          "count": {
            "type": "integer",
            "description": "Distinct qualifying machines; zero only for a successful empty count.",
            "format": "int64"
          },
          "measuredAtUtc": {
            "type": "string",
            "description": "UTC instant used for both strict time comparisons.",
            "format": "date-time"
          },
          "refreshAfterSeconds": {
            "type": "integer",
            "description": "Recommended polling interval, 15 seconds.",
            "format": "int32"
          }
        },
        "additionalProperties": false,
        "description": "Public count of currently running machines."
      },
      "UnitProgressResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "nullable": true
          },
          "percent": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "remainingSeconds": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "calculatedAtUtc": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": false,
        "description": "API-calculated progress. Percent and remaining time are estimates, not hardware completion signals."
      },
      "ValidationProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "instance": {
            "type": "string",
            "nullable": true
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "nullable": true
          }
        },
        "additionalProperties": { }
      }
    }
  },
  "tags": [
    {
      "name": "App downloads"
    },
    {
      "name": "Circles"
    },
    {
      "name": "Gradients"
    },
    {
      "name": "Icons"
    },
    {
      "name": "Logos"
    },
    {
      "name": "Public"
    }
  ]
}