{
  "openapi": "3.0.4",
  "info": {
    "title": "Kombine Flex Portal — Public API",
    "description": "Anonymous status, cached statistics and purchase map. No token or login is required. The hostname selects the tenant; callers cannot select another tenant. Status does not check database readiness. See each operation for caching, limitations and error responses. [Dansk vejledning](../docs/da) · [English guide](../docs).",
    "version": "v1"
  },
  "paths": {
    "/api/v1/public/displays/disp73": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Shows recent purchases as coin markers on a map. No login required.",
        "description": "Returns a newest-first list of at most 200 purchases retained in Log1Hour, without a timestamp filter.\nTimestampUtc is the original MS2000 converted directly to UTC.\nThis is a visual sample, not accounting data: missing coordinates can omit purchases.\nAmounts are positive major currency units; currencies are not converted. Transfers and rows whose Text ends in E are excluded.\nKid is a canonical transaction KID with the original timestamp; use it to suppress duplicate animations.\nKIDs identify the tenant/bank/location/unit event, grant no permissions and contain no user ID.\nNo names, tags or transaction text are returned.\nOne shared ten-second cache per API instance, thirty-second backoff on failure.\nLimit defaults to 100 and accepts 1–200. RefreshAfterSeconds is always 10 on success.\nNo tenant, bank or time-window override is supported. On 503 retry after thirty 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": "Uses eUserId.Users through eUserId.UsersLast inclusive (currently 1001–99999), Text NOT LIKE '%E', and Amount < 0.\nCounts rows, not distinct users. Log1Hour contains recent transactions and is cleaned every minute by the database.\nThe one-hour window relies on that maintenance; sinceUtc is the nominal start at measurement time.\nCount, -SUM(Amount)/100 and MAX(Currency) are cached for one minute per API instance.\nNo currency conversion is performed; MAX(Currency) assumes matching transactions share a currency.\nNo tenant or filter override is accepted. On 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": "Counts distinct (BankId, UserId) pairs with BankId >= 1000 and UserId from 1001 through eUserId.UsersLast (99999), inclusive.\nActivity means a Log7 record's MS2000 exceeds the UTC 100-day cutoff, not a verified login.\nThe site chooses the tenant; no caller-supplied KID, dates or filters are accepted.\nResults are cached for 5 minutes per API instance. Concurrent requests share one query.\nA failed refresh returns 503, not zero; wait 60 seconds before retrying. No personal data is returned.",
        "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.",
        "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."
      },
      "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": "Public"
    }
  ]
}