{
  "openapi": "3.1.0",
  "info": {
    "title": "OpenAnalyticsAPI",
    "description": "Privacy-first analytics API. Web, App, and Server-side tracking without Google.",
    "version": "1.0.0",
    "contact": {
      "name": "OpenAnalyticsAPI Support",
      "url": "https://www.openanalyticsapi.com",
      "email": "support@openanalyticsapi.com"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://api.openanalyticsapi.com",
      "description": "Production"
    }
  ],
  "security": [
    { "BearerAuth": [] }
  ],
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key in format `oa_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "example": "Project not found" }
        }
      },
      "Project": {
        "type": "object",
        "properties": {
          "id":                  { "type": "integer", "example": 42 },
          "name":                { "type": "string",  "example": "My Website" },
          "site_id":             { "type": "string",  "example": "a7b3c9d2e1f4" },
          "type":                { "type": "string",  "enum": ["web","ios","android","react_native","flutter","server"] },
          "domain":              { "type": ["string","null"], "example": "example.com" },
          "bundle_id":           { "type": ["string","null"], "example": "com.example.app" },
          "timezone":            { "type": "string",  "example": "Europe/Prague" },
          "currency":            { "type": "string",  "example": "USD" },
          "cookieless":          { "type": "boolean" },
          "ip_anonymize":        { "type": "boolean" },
          "replay_enabled":      { "type": "boolean" },
          "replay_sample_rate":  { "type": "number",  "example": 0.1 },
          "heatmap_enabled":     { "type": "boolean" },
          "public_dashboard":    { "type": "boolean" },
          "created_at":          { "type": "string",  "format": "date-time" }
        }
      },
      "PaginationMeta": {
        "type": "object",
        "properties": {
          "total":    { "type": "integer" },
          "page":     { "type": "integer" },
          "per_page": { "type": "integer" },
          "pages":    { "type": "integer" }
        }
      },
      "PageviewsResponse": {
        "type": "object",
        "properties": {
          "project_id": { "type": "integer" },
          "from":       { "type": "string", "format": "date-time" },
          "to":         { "type": "string", "format": "date-time" },
          "group_by":   { "type": "string" },
          "total":      { "type": "integer" },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date":      { "type": "string" },
                "pageviews": { "type": "integer" },
                "visitors":  { "type": "integer" }
              }
            }
          }
        }
      },
      "RealtimeResponse": {
        "type": "object",
        "description": "Live stats derived from the Redis recent-events buffer (last ~500 events per project). Active window = 5 min, pageviews/min window = 60 s.",
        "properties": {
          "project_id":         { "type": "integer" },
          "active_users":       { "type": "integer" },
          "pageviews_per_min":  { "type": "integer" },
          "top_pages": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path":  { "type": "string" },
                "count": { "type": "integer" }
              }
            }
          },
          "top_countries": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "country_code": { "type": "string" },
                "count":        { "type": "integer" }
              }
            }
          },
          "last_events": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type":        { "type": "string" },
                "name":        { "type": "string" },
                "path":        { "type": "string" },
                "country":     { "type": "string" },
                "device_type": { "type": "string" },
                "source":      { "type": "string" },
                "timestamp":   { "type": ["string","null"] }
              }
            }
          },
          "updated_at":      { "type": ["string","null"], "format": "date-time" },
          "window_active_s": { "type": "integer", "example": 300 },
          "window_pv_s":     { "type": "integer", "example": 60 }
        }
      },
      "SdkRelease": {
        "type": "object",
        "properties": {
          "version":      { "type": "string", "example": "1.2.0" },
          "download_url": { "type": "string", "example": "https://cdn.openanalyticsapi.com/sdk/ios/1.2.0.zip" },
          "sha256":       { "type": "string" },
          "size_bytes":   { "type": "integer" },
          "released_at":  { "type": "string", "format": "date-time" }
        }
      },
      "PrivacyRequest": {
        "type": "object",
        "description": "A DSAR request row. Deletion requests are processed asynchronously — creating one never mutates ClickHouse inline, and the current processor is dry-run only (no destructive deletion is implemented yet).",
        "properties": {
          "id":           { "type": "integer", "example": 123 },
          "project_id":   { "type": "integer", "example": 42 },
          "user_id":      { "type": "string",  "example": "sha256_user_hash" },
          "request_type": { "type": "string",  "enum": ["export","delete"] },
          "status":       { "type": "string",  "enum": ["pending","processing","completed","failed"] },
          "created_at":   { "type": "string",  "format": "date-time" },
          "updated_at":   { "type": "string",  "format": "date-time" },
          "completed_at": { "type": ["string","null"], "format": "date-time" },
          "estimates": {
            "type": "object",
            "description": "Cheap, read-only footprint estimate — how much data a future deletion would touch. Never mutates anything.",
            "properties": {
              "estimated_events_count":          { "type": "integer" },
              "estimated_sessions_count":        { "type": "integer" },
              "estimated_replay_sessions_count": { "type": "integer" },
              "estimated_heatmap_events_count":  { "type": ["integer","null"], "description": "Null on the status endpoint — heatmap needs the session_id set, which is computed by the dry-run processor instead." },
              "estimates_note":                  { "type": "string" }
            }
          }
        }
      },
      "PrivacyExport": {
        "type": "object",
        "description": "Sanitized per-user data export. Raw IP, raw User-Agent, properties values, precise geo, and raw replay chunk content are never included. Capped at the last 90 days and 10 000 events by default — `truncated` is true when the cap is hit.",
        "properties": {
          "project_id":  { "type": "integer" },
          "user_id":     { "type": "string" },
          "range":       { "type": "object", "properties": { "from": { "type": "string" }, "to": { "type": "string" } } },
          "event_count": { "type": "integer", "description": "Total matching events (may exceed the number returned in `events`)." },
          "truncated":   { "type": "boolean" },
          "events":      { "type": "array", "items": { "type": "object", "additionalProperties": true } },
          "sessions":    { "type": "array", "items": { "type": "object", "additionalProperties": true } },
          "replay_sessions": { "type": "array", "description": "Replay metadata only — no raw rrweb chunk content.", "items": { "type": "object", "additionalProperties": true } },
          "heatmap_summary": { "type": "object", "description": "Aggregate count summary; heatmap rows have no user_id so they are linked via the user's session_ids. No raw coordinates.", "additionalProperties": true },
          "excluded_fields": { "type": "array", "items": { "type": "string" } },
          "generated_at": { "type": "string", "format": "date-time" }
        }
      },
      "CollectPayload": {
        "type": "object",
        "required": ["s", "t"],
        "properties": {
          "s":   { "type": "string",  "description": "site_id",      "example": "a7b3c9d2e1f4" },
          "t":   { "type": "string",  "description": "event type",   "example": "pageview" },
          "n":   { "type": "string",  "description": "event name",   "example": "purchase" },
          "u":   { "type": "string",  "description": "URL",          "example": "https://example.com/page" },
          "r":   { "type": "string",  "description": "referrer URL", "example": "https://google.com" },
          "sid": { "type": "string",  "description": "session_id" },
          "uid": { "type": "string",  "description": "user_id (hashed)" },
          "p":   { "type": "object",  "description": "custom properties", "additionalProperties": true },
          "d": {
            "type": "object",
            "description": "device/screen info",
            "properties": {
              "sw": { "type": "integer" },
              "sh": { "type": "integer" },
              "vw": { "type": "integer" },
              "vh": { "type": "integer" },
              "l":  { "type": "string" }
            }
          },
          "v":   { "type": "string",  "description": "SDK version",  "example": "1.0.0" },
          "ts":  { "type": "integer", "description": "client timestamp (ms)", "example": 1713532800000 }
        }
      },
      "ServerEventPayload": {
        "type": "object",
        "required": ["event_type"],
        "properties": {
          "event_type":     { "type": "string",  "example": "purchase" },
          "event_name":     { "type": "string",  "example": "Purchase" },
          "url":            { "type": "string",  "example": "https://example.com/checkout" },
          "referrer":       { "type": "string" },
          "session_id":     { "type": "string" },
          "user_id":        { "type": "string" },
          "properties":     { "type": "object",  "additionalProperties": true },
          "revenue":        { "type": "number",  "example": 49.99 },
          "currency":       { "type": "string",  "example": "USD" },
          "language":       { "type": "string",  "example": "en-US" },
          "sdk_version":    { "type": "string",  "example": "server/1.0" },
          "timestamp":      { "type": "integer", "example": 1713532800000 }
        }
      }
    },
    "parameters": {
      "ProjectId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": { "type": "integer" },
        "description": "Project ID"
      },
      "FromDate": {
        "name": "from",
        "in": "query",
        "schema": { "type": "string", "format": "date" },
        "description": "Start date (YYYY-MM-DD), defaults to 7 days ago"
      },
      "ToDate": {
        "name": "to",
        "in": "query",
        "schema": { "type": "string", "format": "date" },
        "description": "End date (YYYY-MM-DD), defaults to today"
      },
      "Page": {
        "name": "page",
        "in": "query",
        "schema": { "type": "integer", "default": 1 }
      },
      "PerPage": {
        "name": "per_page",
        "in": "query",
        "schema": { "type": "integer", "default": 50 }
      },
      "Platform": {
        "name": "platform",
        "in": "path",
        "required": true,
        "schema": { "type": "string", "enum": ["flutter","ios","android","react_native"] },
        "description": "Mobile SDK platform"
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Forbidden": {
        "description": "API access not enabled or insufficient permissions",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Rate limit exceeded",
        "headers": {
          "Retry-After": { "schema": { "type": "integer" } },
          "X-RateLimit-Limit": { "schema": { "type": "integer" } },
          "X-RateLimit-Remaining": { "schema": { "type": "integer" } }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  },
  "paths": {
    "/collect": {
      "post": {
        "summary": "Ingest a tracking event",
        "description": "Primary ingest endpoint used by tracking JS, mobile SDKs, and server-side snippets. No authentication required — events are tied to a project via site_id.",
        "operationId": "collectEvent",
        "security": [],
        "tags": ["Ingest"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CollectPayload" },
              "example": {
                "s": "a7b3c9d2e1f4",
                "t": "pageview",
                "u": "https://example.com/pricing",
                "r": "https://google.com/search?q=analytics",
                "sid": "xyz123abc",
                "uid": "anon_hash_abc123",
                "d": { "sw": 1920, "sh": 1080, "vw": 1280, "vh": 720, "l": "en-US" },
                "v": "1.0.0",
                "ts": 1713532800000
              }
            }
          }
        },
        "responses": {
          "204": { "description": "Event accepted" }
        }
      },
      "get": {
        "summary": "1x1 pixel tracking (fallback)",
        "description": "Tracking pixel for environments where JS is unavailable. Pass site_id as query param `s`.",
        "operationId": "collectPixel",
        "security": [],
        "tags": ["Ingest"],
        "parameters": [
          { "name": "s", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "u", "in": "query", "schema": { "type": "string" } },
          { "name": "r", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "1x1 GIF pixel",
            "content": { "image/gif": {} }
          }
        }
      }
    },
    "/collect/batch": {
      "post": {
        "summary": "Ingest a batch of tracking events",
        "description": "Public batch ingest used by mobile SDKs and server-side snippets. No authentication required — events are tied to a project via site_id. Returns 204 by default; pass `?response=json` to get a 200 JSON summary instead. Envelope-level errors (not a non-empty array, over 500 events) return structured 4xx.",
        "operationId": "collectBatch",
        "security": [],
        "tags": ["Ingest"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/CollectPayload" },
                "maxItems": 500
              }
            }
          }
        },
        "responses": {
          "204": { "description": "Batch accepted (default, minimal-bandwidth response)" },
          "200": { "description": "Batch accepted, JSON summary (when `?response=json` is passed)" },
          "400": { "description": "Body is not a non-empty JSON array of events", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "413": { "description": "More than 500 events in one batch", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/replay": {
      "post": {
        "summary": "Upload a session replay chunk",
        "description": "Public endpoint used by the rrweb-based replay addon (oa-replay.js). Replay is uploaded in ~5-second chunks. No authentication — tied to a project via site_id. Always returns 204, even for unknown site_id, bot User-Agents, or malformed payloads (silent contract, same as /collect).",
        "operationId": "uploadReplayChunk",
        "security": [],
        "tags": ["Ingest"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["s", "sid", "events"],
                "properties": {
                  "s":      { "type": "string", "description": "site_id", "example": "a7b3c9d2e1f4" },
                  "sid":    { "type": "string", "description": "session_id" },
                  "uid":    { "type": "string", "description": "anonymous user_id" },
                  "events": { "type": "array", "description": "rrweb event objects for this chunk", "items": { "type": "object", "additionalProperties": true } }
                }
              }
            }
          }
        },
        "responses": {
          "204": { "description": "Chunk accepted or silently discarded" }
        }
      }
    },
    "/v1/sdk/{platform}/latest": {
      "get": {
        "summary": "Get the latest SDK release for a platform",
        "description": "Public, unauthenticated SDK discovery endpoint. Returns metadata for the most recent published release of a mobile SDK.",
        "operationId": "getLatestSdkRelease",
        "security": [],
        "tags": ["SDK"],
        "parameters": [ { "$ref": "#/components/parameters/Platform" } ],
        "responses": {
          "200": {
            "description": "Latest release metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "platform":     { "type": "string" },
                    "version":      { "type": "string" },
                    "download_url": { "type": "string" },
                    "file_path":    { "type": "string" },
                    "sha256":       { "type": "string" },
                    "size_bytes":   { "type": "integer" },
                    "released_at":  { "type": "string", "format": "date-time" },
                    "notes":        { "type": ["string","null"] }
                  }
                }
              }
            }
          },
          "400": { "description": "Unknown platform", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "No release published for this platform", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/v1/sdk/{platform}/releases": {
      "get": {
        "summary": "List SDK releases for a platform",
        "description": "Public, unauthenticated SDK discovery endpoint. Returns all published releases for a mobile SDK, newest first.",
        "operationId": "listSdkReleases",
        "security": [],
        "tags": ["SDK"],
        "parameters": [ { "$ref": "#/components/parameters/Platform" } ],
        "responses": {
          "200": {
            "description": "Release list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "platform": { "type": "string" },
                    "count":    { "type": "integer" },
                    "releases": { "type": "array", "items": { "$ref": "#/components/schemas/SdkRelease" } }
                  }
                }
              }
            }
          },
          "400": { "description": "Unknown platform", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/v1/projects": {
      "get": {
        "summary": "List projects",
        "operationId": "listProjects",
        "tags": ["Projects"],
        "parameters": [
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/PerPage" },
          { "name": "type",   "in": "query", "schema": { "type": "string" } },
          { "name": "search", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "List of projects",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Project" } },
                    "meta": { "$ref": "#/components/schemas/PaginationMeta" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "summary": "Create a project",
        "operationId": "createProject",
        "tags": ["Projects"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name", "type"],
                "properties": {
                  "name":      { "type": "string",  "example": "My Website" },
                  "type":      { "type": "string",  "enum": ["web","ios","android","react_native","flutter","server"] },
                  "domain":    { "type": "string",  "example": "example.com" },
                  "bundle_id": { "type": "string",  "example": "com.example.app" },
                  "timezone":  { "type": "string",  "example": "Europe/Prague" },
                  "currency":  { "type": "string",  "example": "USD" }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Project created",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Project" } } }
          },
          "400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/projects/{id}": {
      "get": {
        "summary": "Get a project",
        "operationId": "getProject",
        "tags": ["Projects"],
        "parameters": [ { "$ref": "#/components/parameters/ProjectId" } ],
        "responses": {
          "200": { "description": "Project detail", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Project" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "summary": "Update a project",
        "operationId": "updateProject",
        "tags": ["Projects"],
        "parameters": [ { "$ref": "#/components/parameters/ProjectId" } ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name":               { "type": "string" },
                  "domain":             { "type": "string" },
                  "timezone":           { "type": "string" },
                  "currency":           { "type": "string" },
                  "cookieless":         { "type": "boolean" },
                  "ip_anonymize":       { "type": "boolean" },
                  "replay_enabled":     { "type": "boolean" },
                  "replay_sample_rate": { "type": "number" },
                  "heatmap_enabled":    { "type": "boolean" },
                  "public_dashboard":   { "type": "boolean" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated project", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Project" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "summary": "Archive a project",
        "operationId": "deleteProject",
        "tags": ["Projects"],
        "parameters": [ { "$ref": "#/components/parameters/ProjectId" } ],
        "responses": {
          "200": { "description": "Project archived" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/projects/{id}/secrets/rotate": {
      "post": {
        "summary": "Rotate the project secret",
        "description": "Generates a new project secret (used as the `X-OA-Secret` header for server-side /collect calls) and invalidates the previous one. The plaintext secret is returned ONCE in the response.",
        "operationId": "rotateProjectSecret",
        "tags": ["Projects"],
        "parameters": [ { "$ref": "#/components/parameters/ProjectId" } ],
        "responses": {
          "200": {
            "description": "New project secret",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "project_id":     { "type": "integer" },
                    "project_secret": { "type": "string", "example": "oaa_secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" },
                    "rotated_at":     { "type": "string", "format": "date-time" },
                    "note":           { "type": "string" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/projects/{id}/events": {
      "get": {
        "summary": "Query events",
        "operationId": "queryEvents",
        "tags": ["Analytics"],
        "parameters": [
          { "$ref": "#/components/parameters/ProjectId" },
          { "$ref": "#/components/parameters/FromDate" },
          { "$ref": "#/components/parameters/ToDate" },
          { "name": "filter", "in": "query", "schema": { "type": "string" }, "description": "Filter expression, e.g. event_type=purchase" },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/PerPage" }
        ],
        "responses": {
          "200": { "description": "Events list" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "post": {
        "summary": "Ingest a single server-side event",
        "operationId": "ingestEvent",
        "tags": ["Server-side Ingest"],
        "parameters": [ { "$ref": "#/components/parameters/ProjectId" } ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServerEventPayload" } } }
        },
        "responses": {
          "202": { "description": "Event queued" },
          "400": { "description": "Validation error" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/projects/{id}/events/batch": {
      "post": {
        "summary": "Ingest a batch of server-side events",
        "operationId": "ingestBatch",
        "tags": ["Server-side Ingest"],
        "parameters": [ { "$ref": "#/components/parameters/ProjectId" } ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["events"],
                "properties": {
                  "events": {
                    "type": "array",
                    "items": { "$ref": "#/components/schemas/ServerEventPayload" },
                    "maxItems": 1000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": { "description": "Events queued" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/projects/{id}/events/recent": {
      "get": {
        "summary": "Recent events buffer",
        "description": "Returns the most recent events for a project from the Redis recent-events buffer (500-event cap per project, 24h TTL). Intended for install verification and live debugging — for historical data use /pageviews or /events.",
        "operationId": "recentEvents",
        "tags": ["Analytics"],
        "parameters": [
          { "$ref": "#/components/parameters/ProjectId" },
          { "name": "limit",   "in": "query", "schema": { "type": "integer", "default": 20, "minimum": 1, "maximum": 500 } },
          { "name": "minutes", "in": "query", "schema": { "type": "integer", "default": 0, "minimum": 0, "maximum": 1440 }, "description": "Only return events from the last N minutes. 0 = no time filter." }
        ],
        "responses": {
          "200": {
            "description": "Recent events",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "project_id": { "type": "integer" },
                    "site_id":    { "type": "string" },
                    "count":      { "type": "integer" },
                    "limit":      { "type": "integer" },
                    "minutes":    { "type": "integer" },
                    "events":     { "type": "array", "items": { "type": "object", "additionalProperties": true } },
                    "note":       { "type": "string" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "503": { "description": "Recent-events buffer (Redis) unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/v1/projects/{id}/pageviews": {
      "get": {
        "summary": "Query pageviews",
        "operationId": "queryPageviews",
        "tags": ["Analytics"],
        "parameters": [
          { "$ref": "#/components/parameters/ProjectId" },
          { "$ref": "#/components/parameters/FromDate" },
          { "$ref": "#/components/parameters/ToDate" },
          {
            "name": "group_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["day","hour","path","referrer","country","device_type","browser","os"],
              "default": "day"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pageview stats",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PageviewsResponse" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/projects/{id}/sessions": {
      "get": {
        "summary": "Query sessions",
        "operationId": "querySessions",
        "tags": ["Analytics"],
        "parameters": [
          { "$ref": "#/components/parameters/ProjectId" },
          { "$ref": "#/components/parameters/FromDate" },
          { "$ref": "#/components/parameters/ToDate" },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/PerPage" }
        ],
        "responses": {
          "200": { "description": "Sessions list" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/projects/{id}/realtime": {
      "get": {
        "summary": "Get realtime stats",
        "description": "Returns live stats: active users, pageviews/min, top pages, top countries. Updated every ~5 seconds via Redis.",
        "operationId": "getRealtimeStats",
        "tags": ["Analytics"],
        "parameters": [ { "$ref": "#/components/parameters/ProjectId" } ],
        "responses": {
          "200": {
            "description": "Realtime stats",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RealtimeResponse" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/projects/{id}/privacy/user/{user_id}": {
      "get": {
        "summary": "Export a user's data (DSAR, beta)",
        "description": "Synchronous, read-only, sanitized export of one end-user's data for Data Subject Access Requests. Raw IP, raw User-Agent, properties values, precise geo, and raw replay chunk content are never included. Capped at the last 90 days and 10 000 events by default (override with `from`/`to`); `truncated` is true when the cap is hit. This endpoint never writes anything.",
        "operationId": "privacyExportUser",
        "tags": ["Privacy"],
        "parameters": [
          { "$ref": "#/components/parameters/ProjectId" },
          { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "The end-user identifier (the hashed value passed to identify/track calls)." },
          { "$ref": "#/components/parameters/FromDate" },
          { "$ref": "#/components/parameters/ToDate" }
        ],
        "responses": {
          "200": {
            "description": "Sanitized user data export",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PrivacyExport" } } }
          },
          "400": { "description": "Missing or invalid user_id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "summary": "Request deletion of a user's data (DSAR, beta)",
        "description": "Queues an asynchronous deletion/anonymization request for one end-user. Returns 202 — NO data is deleted inline and NO ClickHouse mutation is run by this call. A background worker processes pending requests. Idempotent: if a deletion request for this user is already pending or processing, the existing request is returned with 200 instead of queueing a duplicate.",
        "operationId": "privacyDeleteUser",
        "tags": ["Privacy"],
        "parameters": [
          { "$ref": "#/components/parameters/ProjectId" },
          { "name": "user_id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "The end-user identifier to schedule for deletion." }
        ],
        "responses": {
          "202": {
            "description": "Deletion request accepted and queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "queued":     { "type": "boolean", "example": true },
                    "request_id": { "type": "integer", "example": 123 },
                    "status":     { "type": "string", "example": "pending" },
                    "message":    { "type": "string" }
                  }
                }
              }
            }
          },
          "200": {
            "description": "A deletion request for this user is already pending/processing — the existing request is returned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "queued":         { "type": "boolean", "example": true },
                    "request_id":     { "type": "integer" },
                    "status":         { "type": "string" },
                    "already_queued": { "type": "boolean", "example": true },
                    "message":        { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "description": "Missing or invalid user_id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/projects/{id}/privacy/requests/{request_id}": {
      "get": {
        "summary": "Get the status of a privacy request (beta)",
        "description": "Reads back one DSAR request row, scoped to the caller's project. Use this to poll a deletion request queued via DELETE /v1/projects/{id}/privacy/user/{user_id}.",
        "operationId": "privacyRequestStatus",
        "tags": ["Privacy"],
        "parameters": [
          { "$ref": "#/components/parameters/ProjectId" },
          { "name": "request_id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "The privacy request ID." }
        ],
        "responses": {
          "200": {
            "description": "Privacy request status",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PrivacyRequest" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    }
  },
  "tags": [
    { "name": "Ingest",               "description": "Public event collection endpoints (no authentication)" },
    { "name": "SDK",                  "description": "Public SDK discovery endpoints (no authentication)" },
    { "name": "Projects",             "description": "Manage analytics projects" },
    { "name": "Analytics",            "description": "Query analytics data" },
    { "name": "Server-side Ingest",   "description": "Authenticated server-side event ingest" },
    { "name": "Privacy",              "description": "DSAR / privacy beta endpoints — per-user export and deletion requests" }
  ]
}
