{
  "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.**\n\n- The hostname selects the tenant; callers cannot select another tenant.\n- Status does not check database readiness.\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).\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.\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.\n- Try the [local icon test page](../docs/icons) to compare palettes, text, under-icons and sizes.\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).\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).\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).\n- See each operation for caching, limitations and error responses.\n\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\n- Public presentation only; no login, KID, permissions or database access. Does not measure equipment status.\n- `colors`: 2–4 colors separated by hyphens, such as `22aa88-ffcc33-ee4444`.\n- Colors accept 3/6-digit RGB hex without #, exact eColor names, standard named colors or transparent (case-insensitive).\n- Unknown colors return **400** with code `invalid-circle-parameters`; no color guessing.\n### Rendering and cache\n- Matches the legacy CircleGradient1 angular background: fills the viewport, begins at the bottom and turns clockwise.\n- Pure SVG; no third-party graphics components, scripts, fonts or external assets. No raster formats.\n- Fixed input means complete data: variants may be disk-cached for 24 hours, bounded to 512 files/32 MiB.\n- Cache I/O failure falls back to rendering. Public HTTP cache: 600 seconds; ETag supports **304**.\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.\n`background` fills the disc; `colors` is a 2–4-color gradient over the full 0–100 scale.\n`percent` is an integer from 0 to 100; out-of-range values return 400, not clamped.\nMarks begin at the top and advance clockwise, matching CircleProgress1. Zero has no marks; 100 has all 100.\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.\nColors: 2–4 hyphen-separated 3/6-digit RGB hex values without #, exact eColor names,\nstandard named colors or transparent (case-insensitive), evenly spaced.\nAngle: finite degrees, clockwise from left-to-right (0); 90 is top-to-bottom,\n180 right-to-left, 270 bottom-to-top. Decimal angles use a dot; negative angles and full turns normalize modulo 360.\nPure SVG covering the full canvas; no raster formats, scripts or external assets.\nInvalid parameters return 400 with invalid-gradient-parameters; malformed numbers use validation ProblemDetails.\nPrivate disk cache: 24 hours, up to 512 files/32 MiB; cache I/O failure falls back to rendering.\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.\n            Width/height: 16–4096 pixels each. The gradient spans the full rectangle and retains its angle at the requested dimensions.\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),\nsigned Int64 count and colour (0–16777215) replace only those fields. Other KID fields are preserved.\nCalendar always uses today's day of month in Europe/Copenhagen, overriding supplied text.\nUse the returned iconKid unchanged in /api/v1/icon/{iconSet}/{kid}.svg. An icon-only KID is returned\nas its eIcon name. No login, lookup, grant or write; invalid input returns 400.\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.\nThe set is case-sensitive (for example g or line). Only direct membership is listed:\nunlike image rendering, this catalog does not include fallback assets from other sets.\nUnknown sets return 404. Clients should intersect this catalog with the allowed icons\nsupplied by each business operation. The catalog grants no permission to change an icon.\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.\nSupply a URL-encoded canonical Kombine.Flex.Kid.ToString() value. Kid.Icons[0] is the main icon;\nKid.Icons[1] is the under-icon. A missing second entry or eIcon.none omits the under-icon.\nAdditional entries are ignored for rendering. The API also reads Kid.Count, Kid.Color and Kid.Text.\nIf KID parsing fails, an exact eIcon name (case-insensitive) uses count zero, black and empty text.\nNumeric enum IDs, substring matches, malformed KIDs and undefined icons are not name fallbacks.\nCount at or below zero hides the badge. Positive Int64 counts display in full; the red badge widens\nwith circular end caps and a straight middle, keeping its height and font size. Long labels widen the SVG canvas.\nChoose iconSet explicitly: line or g. The g set preserves its original multicolor artwork.\nMain and under-icon each try the chosen set, then other packaged sets in ordinal alphabetical order.\nUnknown sets or icons absent from every local set return 404; no external or database fallback.\nKid.Color uses the low 24 bits as opaque RGB (0 = black; the high byte is ignored).\nKid.Text is case-sensitive, limited to 128 characters without controls and used by assets with a text box.\nURL-encode path parameters. The canonical KID\nis limited to 2048 characters; other path parameters to 128 characters without controls.\nFormats: svg, png, jpg/jpeg, gif, bmp, tif/tiff, webp, ppm, tga and ico. Unknown formats return SVG.\nRaster output defaults to 128 × 128 pixels; SVG retains its authored viewport.\nFirst requests render to disk; repeated requests use the cache. Public cache: ten minutes.\nETag/Last-Modified support 304. Invalid parameters return 400; concurrency limits return 429;\nunavailable rendering/storage returns 503. Retry transient errors with backoff.\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.\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.\nSame size limits as GetIconImageFromSet. Background accepts Flex/known color names or 3/6-digit\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\n- Public; no login, business KID, Tab, permission or database access.\n- Original KombineLogo1 geometry, serialized with .NET XML; no graphics packages or external resources.\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.\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.\n- `color` accepts 3/6-digit RGB hex without #, exact eColor names, standard color names or transparent, case-insensitively.\n- Unknown colors return **400** with code `invalid-logo-parameters`; no substring or index guesses.\n### Format and cache\n- SVG only. All parameters are path segments, and the URL ends in .svg. No raster fallback.\n- First request renders to a private disk cache; subsequent requests reuse it. All logo inputs are fixed, complete presentation data.\n- Maximum 512 variants/32 MiB, 24-hour lifetime. Cache I/O failure falls back to fresh rendering.\n- Public browser cache: 600 seconds; ETag/If-None-Match supports **304**.\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-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/displays/Map1": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Shows recent purchases as coin markers on a map. No login required.",
        "description": "### Public scope and selection\n            \n- **No login required.** No tenant, bank or time-window override is supported.\n- Returns a newest-first sample of purchases retained in **Log1Hour**, without a timestamp filter.\n- `limit`: **1–200**, default **100**.\n- This is a **visual sample**, not accounting data; missing coordinates can omit purchases.\n            \n### Amounts and identifiers\n            \n- Amounts are positive major currency units; currencies are not converted.\n- Transfers and rows whose Text ends in `E` are excluded.\n- `timestampUtc` is the original MS2000 converted directly to UTC.\n- `kid` is a canonical transaction KID with the original timestamp. Use it to suppress duplicate animations.\n- KIDs identify the tenant/bank/location/unit event, grant no permissions and contain no user ID.\n- No names, tags or transaction text are returned.\n            \n### Refresh and errors\n            \n- One shared **10-second cache** per API instance. `refreshAfterSeconds` is always **10** on success.\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/statistics/purchases": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Counts purchases in the site's Log1Hour without login.",
        "description": "### Public scope and selection\n            \n- **No login required.** The site selects the tenant; no tenant or filter override is accepted.\n- Uses `eUserId.Users` through `eUserId.UsersLast`, inclusive (currently **1001–99999**), `Text NOT LIKE '%E'` and `Amount < 0`.\n- Counts **rows**, not distinct users.\n            \n### Time window and amounts\n            \n- **Log1Hour** holds recent transactions and is cleaned every minute by the database.\n- The one-hour window depends on that maintenance. `sinceUtc` is the nominal start at measurement time.\n- Count, `-SUM(Amount)/100` and `MAX(Currency)` are cached for **one minute** per API instance.\n- No currency conversion is performed; `MAX(Currency)` assumes matching transactions share a currency.\n            \n### Errors\n            \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,
                  "amount": 123.45,
                  "currency": "DKK",
                  "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\n            \n- **No login required.** The site chooses the tenant; no caller-supplied KID, dates or filters are accepted.\n- Counts distinct **(BankId, UserId)** pairs with **BankId ≥ 1000** and UserId **1001–99999** (`eUserId.UsersLast` inclusive).\n- Activity means a current **Log7** record's `MS2000` exceeds the UTC **100-day cutoff**; it is not a verified login.\n- No personal data is returned.\n            \n### Cache and errors\n            \n- Results are cached for **5 minutes** per API instance. Concurrent requests share one query.\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/status": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Gets public API availability. This does not check database readiness.",
        "description": "### Availability\n            \n- **Public; no login required.**\n- Returns the service name, availability and contract version.\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()."
      },
      "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": { }
      },
      "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
          }
        },
        "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"
          },
          "amount": {
            "type": "number",
            "description": "Positive purchase total in major currency units; zero for no matches.",
            "format": "double"
          },
          "currency": {
            "type": "string",
            "description": "MAX(Currency), or null for no matches. No currency conversion.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Public purchase aggregate in Log1Hour, cached for up to one minute."
      },
      "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"
    }
  ]
}