{
  "openapi": "3.1.0",
  "info": { "title": "Actualign HTTP API", "version": "1.1.0", "description": "Privacyvriendelijke opname- en lees-API voor Actualign." },
  "servers": [{ "url": "https://heatmap-tau-nine.vercel.app" }],
  "paths": {
    "/api/v1/events": {
      "post": {
        "summary": "Neem één tot twintig heatmapgebeurtenissen op",
        "operationId": "recordEvents",
        "parameters": [{ "name": "Origin", "in": "header", "required": true, "description": "Moet exact overeenkomen met het websiteadres van het project.", "schema": { "type": "string", "format": "uri" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EventBatch" } } } },
        "responses": { "204": { "description": "Opgeslagen" }, "400": { "description": "Ongeldige aanvraag" }, "403": { "description": "Herkomst niet toegestaan of opnamesleutel ongeldig" }, "413": { "description": "Aanvraag groter dan 24 kB" }, "503": { "$ref": "#/components/responses/Unavailable" } }
      }
    },
    "/api/v1/screens": {
      "get": {
        "summary": "Geef gemeten pagina's en ontwerpversies",
        "operationId": "listScreens",
        "security": [{ "readKey": [] }],
        "responses": { "200": { "description": "Schermen van het project", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScreensResponse" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "503": { "$ref": "#/components/responses/Unavailable" } }
      }
    },
    "/api/v1/heatmap": {
      "get": {
        "summary": "Geef samengevoegde klik- en scrollmetingen",
        "operationId": "getHeatmap",
        "security": [{ "readKey": [] }],
        "parameters": [
          { "name": "screenId", "in": "query", "required": true, "schema": { "type": "string", "maxLength": 200 } },
          { "name": "device", "in": "query", "schema": { "$ref": "#/components/schemas/Device" } },
          { "name": "days", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 90, "default": 30 } }
        ],
        "responses": { "200": { "description": "Samengevoegde metingen", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HeatmapResponse" } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "503": { "$ref": "#/components/responses/Unavailable" } }
      }
    },
    "/api/v1/visits": {
      "get": {
        "summary": "Geef tijdelijke anonieme bezoeken en hun schermgedrag",
        "operationId": "listVisits",
        "security": [{ "readKey": [] }],
        "parameters": [
          { "name": "days", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 7, "default": 7 } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 } },
          { "name": "device", "in": "query", "required": false, "schema": { "$ref": "#/components/schemas/Device" } }
        ],
        "responses": { "200": { "description": "Recente anonieme bezoeken", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VisitsResponse" } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "503": { "$ref": "#/components/responses/Unavailable" } }
      }
    }
  },
  "components": {
    "securitySchemes": { "readKey": { "type": "http", "scheme": "bearer", "description": "Projectgebonden sleutel die begint met hm_read_." } },
    "schemas": {
      "Device": { "type": "string", "enum": ["mobile", "tablet", "desktop"], "default": "mobile" },
      "EventBatch": { "type": "object", "required": ["key", "events"], "properties": { "key": { "type": "string", "pattern": "^hm_ing_" }, "events": { "type": "array", "minItems": 1, "maxItems": 20, "items": { "$ref": "#/components/schemas/HeatmapEvent" } } } },
      "HeatmapEvent": {
        "type": "object", "required": ["screenId", "screenVersion", "stateId", "type", "device", "orientation", "viewportWidth", "viewportHeight", "occurredAt"],
        "properties": {
          "screenId": { "type": "string", "maxLength": 200 }, "screenVersion": { "type": "string", "maxLength": 80 }, "stateId": { "type": "string", "maxLength": 80 },
          "type": { "type": "string", "enum": ["view", "click", "scroll"] }, "device": { "$ref": "#/components/schemas/Device" }, "orientation": { "type": "string", "enum": ["portrait", "landscape"] },
          "viewportWidth": { "type": "integer", "minimum": 200, "maximum": 10000 }, "viewportHeight": { "type": "integer", "minimum": 200, "maximum": 10000 },
          "xRatio": { "type": "number", "minimum": 0, "maximum": 1 }, "yRatio": { "type": "number", "minimum": 0, "maximum": 1 }, "scrollDepth": { "type": "integer", "enum": [25, 50, 75, 100] },
          "elementId": { "type": "string", "maxLength": 80 },
          "visitId": { "type": "string", "format": "uuid", "description": "Willekeurige tijdelijke code voor één bezoek. Alleen samen met visitSequence." },
          "visitSequence": { "type": "integer", "minimum": 1, "maximum": 1000000, "description": "Oplopend volgnummer binnen het tijdelijke bezoek." },
          "occurredAt": { "type": "string", "format": "date-time", "description": "Niet meer dan 24 uur van de servertijd verwijderd." }
        }
      },
      "ScreensResponse": { "type": "object", "required": ["project", "screens"], "properties": { "project": { "type": "object", "required": ["project_id", "tenant_id", "project_name", "allowed_origin"], "properties": { "project_id": { "type": "string", "format": "uuid" }, "tenant_id": { "type": "string", "format": "uuid" }, "project_name": { "type": "string" }, "allowed_origin": { "type": "string", "format": "uri" } } }, "screens": { "type": "array", "items": { "type": "object", "required": ["screen_id", "screen_version", "views", "last_seen"], "properties": { "screen_id": { "type": "string" }, "screen_version": { "type": "string" }, "views": { "type": "integer" }, "last_seen": { "type": "string" } } } } } },
      "HeatmapResponse": { "type": "object", "required": ["project", "screenId", "screenVersion", "device", "days", "views", "clicks", "scroll"], "properties": { "project": { "type": "object", "required": ["id", "name", "allowedOrigin"], "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "allowedOrigin": { "type": "string", "format": "uri" } } }, "screenId": { "type": "string" }, "screenVersion": { "type": ["string", "null"] }, "device": { "$ref": "#/components/schemas/Device" }, "days": { "type": "integer" }, "views": { "type": "integer" }, "clicks": { "type": "array", "items": { "type": "object", "required": ["x", "y", "count", "elementId"], "properties": { "x": { "type": "number" }, "y": { "type": "number" }, "count": { "type": "integer" }, "elementId": { "type": ["string", "null"] } } } }, "scroll": { "type": "object", "additionalProperties": { "type": "integer" } } } },
      "VisitScreen": { "type": "object", "required": ["screenId", "screenVersion", "firstSeenAt", "lastSeenAt", "clicks", "clickedElements", "maxScroll"], "properties": { "screenId": { "type": "string" }, "screenVersion": { "type": "string" }, "firstSeenAt": { "type": "string", "format": "date-time" }, "lastSeenAt": { "type": "string", "format": "date-time" }, "clicks": { "type": "integer" }, "clickedElements": { "type": "array", "items": { "type": "string" } }, "maxScroll": { "type": "integer", "enum": [0, 25, 50, 75, 100] } } },
      "VisitSummary": { "type": "object", "required": ["id", "startedAt", "endedAt", "device", "screenCount", "eventCount", "screens"], "properties": { "id": { "type": "string", "description": "Tijdelijke verkorte projectgebonden code, geen persoon." }, "startedAt": { "type": "string", "format": "date-time" }, "endedAt": { "type": "string", "format": "date-time" }, "device": { "$ref": "#/components/schemas/Device" }, "screenCount": { "type": "integer" }, "eventCount": { "type": "integer" }, "screens": { "type": "array", "items": { "$ref": "#/components/schemas/VisitScreen" } } } },
      "VisitsResponse": { "type": "object", "required": ["project", "days", "device", "retentionDays", "visits", "summary"], "properties": { "project": { "type": "object", "required": ["id", "name", "allowedOrigin"], "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "allowedOrigin": { "type": "string", "format": "uri" } } }, "days": { "type": "integer", "minimum": 1, "maximum": 7 }, "device": { "oneOf": [{ "$ref": "#/components/schemas/Device" }, { "type": "null" }] }, "retentionDays": { "type": "integer", "const": 7 }, "visits": { "type": "array", "items": { "$ref": "#/components/schemas/VisitSummary" } }, "summary": { "type": "object", "required": ["visits", "multiScreenVisits", "averageScreensPerVisit"], "properties": { "visits": { "type": "integer" }, "multiScreenVisits": { "type": "integer" }, "averageScreensPerVisit": { "type": "number" } } } } },
      "Error": { "type": "object", "required": ["error"], "properties": { "error": { "type": "string" } } }
    },
    "responses": {
      "BadRequest": { "description": "Ongeldige aanvraag", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "Aanmelding ontbreekt", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Forbidden": { "description": "Geen toegang of verkeerd websiteadres", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unavailable": { "description": "Dienst tijdelijk niet beschikbaar", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  }
}
