{
  "openapi": "3.1.0",
  "info": {
    "title": "Webcounter.ca Customer API",
    "version": "1.0.0",
    "description": "Read analytics with a session, an API key, or a secret key. Sites, goals, share links, scheduled reports, alert rules, and API keys themselves can be created, edited, and deleted with an owner/admin browser session (with CSRF token) or a secret key. An API key is read-only. Uptime monitoring, Web Push devices, and personal preferences remain session-only regardless of key kind.\n\nEvery JSON response is the house envelope: `{ok: true, data}` or `{ok: false, error: {code, message}}`. Each operation below types its own `data` payload, so field names are discoverable without calling production. Public resource identifiers, including API keys, consistently use the field and path-parameter name `id`. Timestamps are ISO-8601 with an explicit `+00:00` offset EXCEPT on the per-visit reads (`/s/{slug}/realtime`, `/visitors`, `/visitor-map`, `/session/{session_id}`), which return the stored timestamp in RFC 1123 HTTP-date form -- see the HttpDate schema."
  },
  "servers": [{"url": "https://webcounter.ca/api/v1"}],
  "security": [{"bearerAuth": []}, {"secretKeyAuth": []}, {"sessionAuth": []}],
  "tags": [
    {"name": "Sites"}, {"name": "Network"}, {"name": "Analytics"},
    {"name": "Goals"}, {"name": "Funnels"}, {"name": "Insights"}, {"name": "Sharing"}, {"name": "Credentials"},
    {"name": "Monitoring"}, {"name": "Push"}, {"name": "Preferences"},
    {"name": "Reports"}, {"name": "Exports"}, {"name": "Alerts"}, {"name": "Retention"}
  ],
  "paths": {
    "/sites": {
      "get": {"tags":["Sites"],"operationId":"listSites","responses":{"200":{"$ref":"#/components/responses/SiteList"},"401":{"$ref":"#/components/responses/JsonError"}}},
      "post": {"tags":["Sites"],"operationId":"createSite","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"requestBody":{"$ref":"#/components/requestBodies/CreateSite"},"responses":{"200":{"$ref":"#/components/responses/SiteCreated"},"400":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/sites/{slug}": {
      "patch": {"tags":["Sites"],"operationId":"updateSite","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"}],"requestBody":{"$ref":"#/components/requestBodies/UpdateSite"},"responses":{"200":{"$ref":"#/components/responses/SiteUpdated"},"400":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/sites/{slug}/delete": {"post":{"tags":["Sites"],"operationId":"scheduleSiteDeletion","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"}],"responses":{"200":{"$ref":"#/components/responses/SiteDeletionScheduled"},"403":{"$ref":"#/components/responses/JsonError"}}}},
    "/sites/{slug}/restore": {"post":{"tags":["Sites"],"operationId":"restoreSite","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"}],"responses":{"200":{"$ref":"#/components/responses/SiteRestored"},"404":{"$ref":"#/components/responses/JsonError"}}}},
    "/sites/{slug}/validation": {"get":{"tags":["Sites"],"operationId":"validateSiteCollection","parameters":[{"$ref":"#/components/parameters/Slug"}],"responses":{"200":{"$ref":"#/components/responses/SiteValidation"}}}},
    "/sites/{slug}/collection-health": {"get":{"tags":["Sites"],"operationId":"getSiteCollectionHealth","parameters":[{"$ref":"#/components/parameters/Slug"}],"responses":{"200":{"$ref":"#/components/responses/SiteCollectionHealth"}}}},
    "/sites/{slug}/measurement-key": {"post":{"tags":["Credentials"],"operationId":"rotateMeasurementKey","description":"Rotates the optional server-to-server ingest key and returns it once. The previous key stops working immediately.","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"}],"responses":{"200":{"$ref":"#/components/responses/MeasurementKey"},"403":{"$ref":"#/components/responses/JsonError"}}}},
    "/ingest-telemetry": {"get":{"tags":["Network"],"operationId":"getIngestTelemetry","description":"Deployment-wide ingest counters, not scoped to the caller's account -- session-only for that reason, like uptime monitoring. Accepts no bearer key of either kind.","security":[{"sessionAuth":[]}],"responses":{"200":{"$ref":"#/components/responses/IngestTelemetry"},"401":{"$ref":"#/components/responses/JsonError"}}}},
    "/network/overview": {"get":{"tags":["Network"],"operationId":"getNetworkOverview","parameters":[{"$ref":"#/components/parameters/Range"},{"$ref":"#/components/parameters/Start"},{"$ref":"#/components/parameters/End"},{"$ref":"#/components/parameters/Compare"}],"responses":{"200":{"$ref":"#/components/responses/NetworkOverview"}}}},
    "/network/geo": {"get":{"tags":["Network"],"operationId":"getNetworkGeo","parameters":[{"$ref":"#/components/parameters/Range"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Bbox"}],"responses":{"200":{"$ref":"#/components/responses/NetworkGeo"}}}},
    "/network/realtime": {"get":{"tags":["Network"],"operationId":"getNetworkRealtime","responses":{"200":{"$ref":"#/components/responses/NetworkRealtime"}}}},
    "/s/{slug}/overview": {"get":{"tags":["Analytics"],"operationId":"getOverview","parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Range"},{"$ref":"#/components/parameters/Start"},{"$ref":"#/components/parameters/End"},{"$ref":"#/components/parameters/FilterDimension"},{"$ref":"#/components/parameters/FilterValue"}],"responses":{"200":{"$ref":"#/components/responses/Overview"},"400":{"$ref":"#/components/responses/JsonError"}}}},
    "/s/{slug}/timeseries": {"get":{"tags":["Analytics"],"operationId":"getTimeseries","parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Range"},{"$ref":"#/components/parameters/Interval"},{"$ref":"#/components/parameters/Compare"},{"$ref":"#/components/parameters/FilterDimension"},{"$ref":"#/components/parameters/FilterValue"}],"responses":{"200":{"$ref":"#/components/responses/Timeseries"},"400":{"$ref":"#/components/responses/JsonError"}}}},
    "/s/{slug}/dimension/{dimension}": {"get":{"tags":["Analytics"],"operationId":"getDimension","description":"Ranked values for one dimension. Rows carry `pageviews` for every event-backed dimension and `sessions` instead for the two session-backed ones (entry_page, exit_page); `visitors` is always present. filter_dimension/filter_value (IMP-0124) scope the ranking to one cross-panel drill-down segment.","parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Dimension"},{"$ref":"#/components/parameters/Range"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/FilterDimension"},{"$ref":"#/components/parameters/FilterValue"}],"responses":{"200":{"$ref":"#/components/responses/DimensionRanking"},"400":{"$ref":"#/components/responses/JsonError"}}}},
    "/s/{slug}/geo": {"get":{"tags":["Analytics"],"operationId":"getGeo","description":"One aggregated dot per city for the map, plus a whole-world country ranking beside it. The country roll-up ignores bbox on purpose -- it backs a ranked table, not the dots.","parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Range"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Bbox"},{"$ref":"#/components/parameters/FilterDimension"},{"$ref":"#/components/parameters/FilterValue"}],"responses":{"200":{"$ref":"#/components/responses/SiteGeo"},"400":{"$ref":"#/components/responses/JsonError"}}}},
    "/s/{slug}/realtime": {"get":{"tags":["Analytics"],"operationId":"getRealtime","parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Minutes"}],"responses":{"200":{"$ref":"#/components/responses/Realtime"}}}},
    "/s/{slug}/visitors": {"get":{"tags":["Analytics"],"operationId":"getVisitors","description":"Recent sessions, newest first. Rows are the stored session document, so the id field is `_id` and the timestamps are RFC 1123 HTTP-dates rather than ISO-8601.","parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Range"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/FilterDimension"},{"$ref":"#/components/parameters/FilterValue"}],"responses":{"200":{"$ref":"#/components/responses/VisitorSessionPage"},"400":{"$ref":"#/components/responses/JsonError"}}}},
    "/s/{slug}/visitor-map": {"get":{"tags":["Analytics"],"operationId":"getVisitorMap","description":"One pin per locatable visit, newest first -- the counterpart to /geo, which aggregates to one sized dot per city. Sessions with no coordinates are excluded, so `total` counts locatable visits only.","parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Range"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/FilterDimension"},{"$ref":"#/components/parameters/FilterValue"}],"responses":{"200":{"$ref":"#/components/responses/VisitorMapPage"},"400":{"$ref":"#/components/responses/JsonError"}}}},
    "/s/{slug}/session/{session_id}": {"get":{"tags":["Analytics"],"operationId":"getSessionJourney","description":"One stored session document plus its ordered hits. Same caveat as /visitors: the id field is `_id` and timestamps are RFC 1123 HTTP-dates.","parameters":[{"$ref":"#/components/parameters/Slug"},{"name":"session_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/VisitorSessionDetail"},"404":{"$ref":"#/components/responses/JsonError"}}}},
    "/s/{slug}/events": {"get":{"tags":["Analytics"],"operationId":"getCustomEvents","parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Range"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"$ref":"#/components/responses/CustomEventList"}}}},
    "/s/{slug}/goals": {
      "get":{"tags":["Goals"],"operationId":"listGoals","description":"Per-goal conversion counts for the period. This is the one route whose filter_dimension is limited to the eight segment dimensions copied verbatim onto the session (referrer, referrer_source, country, region, city, browser, os, device); the wider set every other route accepts returns 400 here.","parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Range"},{"$ref":"#/components/parameters/FilterDimension"},{"$ref":"#/components/parameters/FilterValue"}],"responses":{"200":{"$ref":"#/components/responses/GoalConversionList"},"400":{"$ref":"#/components/responses/JsonError"}}},
      "post":{"tags":["Goals"],"operationId":"createGoal","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"}],"requestBody":{"$ref":"#/components/requestBodies/CreateGoal"},"responses":{"200":{"$ref":"#/components/responses/GoalCreated"},"400":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/s/{slug}/goals/{name}": {
      "get":{"tags":["Goals"],"operationId":"getGoalDetail","description":"One goal's conversion trend, its converting landing pages, and the campaigns that brought them (IMP-0086). `trend` is zero-filled across the whole period.","parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Range"},{"name":"name","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Interval"}],"responses":{"200":{"$ref":"#/components/responses/GoalDetail"},"404":{"$ref":"#/components/responses/JsonError"}}},
      "delete":{"tags":["Goals"],"operationId":"deleteGoal","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"},{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/Deleted"},"404":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/s/{slug}/funnels": {
      "get":{"tags":["Funnels"],"operationId":"listFunnels","description":"Funnel definitions only, no per-period counts — see getFunnelDetail for the step report.","parameters":[{"$ref":"#/components/parameters/Slug"}],"responses":{"200":{"$ref":"#/components/responses/FunnelList"}}},
      "post":{"tags":["Funnels"],"operationId":"createFunnel","description":"Ordered steps (IMP-0125), 2-5 of them, each a goal-like {name, type, pattern} matcher evaluated in sequence.","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"}],"requestBody":{"$ref":"#/components/requestBodies/CreateFunnel"},"responses":{"200":{"$ref":"#/components/responses/Funnel"},"400":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/s/{slug}/funnels/{name}": {
      "get":{"tags":["Funnels"],"operationId":"getFunnelDetail","description":"Per-step session counts and drop-off for the period, in step order, plus a low_sample flag below sample_size_floor entrants and a Wilson-score conversion_rate_ci per step (IMP-0175). A step's label is reported as `value`, not `name`. compare=previous (the same toggle /overview and /timeseries read) additionally scores the immediately preceding period of equal length and adds previous_entered, previous_low_sample, and per step previous_count/previous_conversion_rate/conversion_rate_change_pct.","parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Range"},{"name":"name","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Compare"}],"responses":{"200":{"$ref":"#/components/responses/FunnelReport"},"404":{"$ref":"#/components/responses/JsonError"}}},
      "delete":{"tags":["Funnels"],"operationId":"deleteFunnel","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"},{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/Deleted"},"404":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/s/{slug}/insights": {"get":{"tags":["Insights"],"operationId":"listInsights","description":"IMP-0094. Deterministic, rule-based findings for the period (no LLM): significant traffic change vs. a baseline-day median of the same weekday's periods (IMP-0207), goal conversion-rate shifts, new/departed top referrers and pages, and bounce-rate shifts. A co-occurring traffic_change and bounce_rate_change fold into one combined card (IMP-0207) whose `rule` is the two rule names joined by '+'. Each finding cites real numbers in `narrative`, carries a `confidence` tier and a `drilldown_link`, and is a stable `fingerprint` for dismiss/feedback. `evidence` is a per-rule bag of the numbers the narrative quotes and is deliberately untyped. Already-dismissed findings are omitted. filter_dimension/filter_value (IMP-0207) scope every detector to one cross-panel drill-down segment, same as /overview.","parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Range"},{"$ref":"#/components/parameters/Start"},{"$ref":"#/components/parameters/End"},{"$ref":"#/components/parameters/FilterDimension"},{"$ref":"#/components/parameters/FilterValue"}],"responses":{"200":{"$ref":"#/components/responses/InsightList"},"400":{"$ref":"#/components/responses/JsonError"}}}},
    "/s/{slug}/insights/{fingerprint}/dismiss": {"post":{"tags":["Insights"],"operationId":"dismissInsight","description":"Hides this finding (by content fingerprint, not a stored id) for 60 days. Site-scoped: any signed-in viewer of the site sees it hidden, matching a shared panel rather than a per-user preference. An optional `rule` query parameter (IMP-0207) tags the action for chronic_not_helpful_rules' reporting; omitting it still dismisses correctly.","security":[{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"},{"name":"fingerprint","in":"path","required":true,"schema":{"type":"string"}},{"name":"rule","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/InsightDismissed"}}}},
    "/s/{slug}/insights/{fingerprint}/feedback": {"post":{"tags":["Insights"],"operationId":"feedbackInsight","description":"Records helpful/not_helpful for a finding; the latest vote wins. Logged for later review, not read back into detection. An optional `rule` field in the JSON body (IMP-0207) tags the action for chronic_not_helpful_rules' reporting.","security":[{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"},{"name":"fingerprint","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"$ref":"#/components/requestBodies/InsightFeedback"},"responses":{"200":{"$ref":"#/components/responses/InsightFeedback"},"400":{"$ref":"#/components/responses/JsonError"}}}},
    "/insights/muted-rules": {
      "get":{"tags":["Insights"],"operationId":"listMutedInsightRules","description":"The account's explicit insight opt-outs, plus every rule that may be muted. Separate from the feedback-summary candidates: a rule appearing there is a suggestion, this is the setting.","security":[{"sessionAuth":[]}],"responses":{"200":{"$ref":"#/components/responses/MutedInsightRules"}}},
      "patch":{"tags":["Insights"],"operationId":"replaceMutedInsightRules","description":"Replace the whole opt-out list; send [] to mute nothing. Admin/owner only -- feedback votes never reach this route.","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"requestBody":{"$ref":"#/components/requestBodies/MutedInsightRules"},"responses":{"200":{"$ref":"#/components/responses/MutedInsightRulesUpdated"},"400":{"$ref":"#/components/responses/JsonError"},"404":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/insights/feedback-summary": {"get":{"tags":["Insights"],"operationId":"getInsightsFeedbackSummary","description":"IMP-0207. Insight rules the caller's visible sites have rated persistently unhelpful (>=CHRONIC_MIN_FEEDBACK votes, >=CHRONIC_NOT_HELPFUL_RATIO not_helpful) — candidates an operator may want to mute. Read-only: never changes what /insights itself returns for any site.","security":[{"sessionAuth":[]}],"responses":{"200":{"$ref":"#/components/responses/InsightFeedbackSummary"}}}},
    "/s/{slug}/export.csv": {"get":{"tags":["Analytics"],"operationId":"exportCsv","description":"A ranked dimension as CSV, up to 500 rows. The header row is the row shape getDimension returns: value,pageviews,visitors — or value,sessions,visitors for entry_page/exit_page. An empty range still emits the value,pageviews,visitors header.","parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Range"},{"name":"dimension","in":"query","required":true,"schema":{"$ref":"#/components/schemas/DimensionName"}},{"$ref":"#/components/parameters/FilterDimension"},{"$ref":"#/components/parameters/FilterValue"}],"responses":{"200":{"description":"CSV export","content":{"text/csv":{"schema":{"type":"string"}}}},"400":{"$ref":"#/components/responses/JsonError"}}}},
    "/s/{slug}/exports": {
      "get":{"tags":["Exports"],"operationId":"listExports","parameters":[{"$ref":"#/components/parameters/Slug"}],"responses":{"200":{"$ref":"#/components/responses/ExportJobList"}}},
      "post":{"tags":["Exports"],"operationId":"createExport","description":"Queue a full-fidelity CSV export of raw events or sessions. Processed asynchronously by scripts/process_exports.py; poll GET .../exports/{export_id} for state.","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"}],"requestBody":{"$ref":"#/components/requestBodies/CreateExport"},"responses":{"200":{"$ref":"#/components/responses/ExportJob"},"400":{"$ref":"#/components/responses/JsonError"},"429":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/s/{slug}/exports/{export_id}": {
      "get":{"tags":["Exports"],"operationId":"getExport","parameters":[{"$ref":"#/components/parameters/Slug"},{"name":"export_id","in":"path","required":true,"description":"The `id` field of an ExportJob.","schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/ExportJob"},"404":{"$ref":"#/components/responses/JsonError"}}},
      "delete":{"tags":["Exports"],"operationId":"deleteExport","description":"Cancel a queued export or delete a finished one, removing its file.","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"},{"name":"export_id","in":"path","required":true,"description":"The `id` field of an ExportJob.","schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/Deleted"},"404":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/s/{slug}/exports/{export_id}/download": {"get":{"tags":["Exports"],"operationId":"downloadExport","description":"The finished file. Carries X-Checksum-SHA256 matching the job's checksum_sha256.","parameters":[{"$ref":"#/components/parameters/Slug"},{"name":"export_id","in":"path","required":true,"description":"The `id` field of an ExportJob.","schema":{"type":"string"}}],"responses":{"200":{"description":"CSV export file","content":{"text/csv":{"schema":{"type":"string"}}}},"404":{"$ref":"#/components/responses/JsonError"}}}},
    "/share-links": {
      "get":{"tags":["Sharing"],"operationId":"listShareLinks","description":"Every share link on a site the caller may administer. The token is never returned by the listing -- only createShareLink ever emits a working URL, once.","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"responses":{"200":{"$ref":"#/components/responses/ShareLinkList"}}},
      "post":{"tags":["Sharing"],"operationId":"createShareLink","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"requestBody":{"$ref":"#/components/requestBodies/CreateShareLink"},"responses":{"200":{"$ref":"#/components/responses/ShareLinkCreated"},"400":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/share-links/{link_id}": {"delete":{"tags":["Sharing"],"operationId":"deleteShareLink","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"name":"link_id","in":"path","required":true,"description":"The `id` field of a ShareLink row.","schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/Revoked"},"404":{"$ref":"#/components/responses/JsonError"}}}},
    "/api-keys": {
      "get":{"tags":["Credentials"],"operationId":"listApiKeys","description":"Account-wide secret keys only; a site-bound secret key gets 403. A row's `id` IS the public key_id half of the credential and is the value the {id} path parameter of deleteApiKey takes -- the listing has no separate `key_id` field.","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"responses":{"200":{"$ref":"#/components/responses/ApiKeyList"}}},
      "post":{"tags":["Credentials"],"operationId":"createApiKey","description":"Account-wide secret keys only; a site-bound secret key gets 403. Minting a new secret key (kind=management) requires a session and the account password in the request body -- a key can never mint a key as powerful as itself. `key` is the only copy of the full credential and is never returned again.","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"requestBody":{"$ref":"#/components/requestBodies/CreateApiKey"},"responses":{"200":{"$ref":"#/components/responses/ApiKeyCreated"},"400":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/api-keys/{id}": {"delete":{"tags":["Credentials"],"operationId":"deleteApiKey","description":"Account-wide secret keys only; a site-bound secret key gets 403. The URL is unchanged; `id` is the public identifier returned by listApiKeys and createApiKey.","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/ApiKeyId"}],"responses":{"200":{"$ref":"#/components/responses/Revoked"},"404":{"$ref":"#/components/responses/JsonError"}}}},
    "/report-schedules": {
      "get":{"tags":["Reports"],"operationId":"listReportSchedules","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"responses":{"200":{"$ref":"#/components/responses/ReportScheduleList"}}},
      "post":{"tags":["Reports"],"operationId":"createReportSchedule","description":"Creates the schedule in state pending_verification and mails the recipient a confirmation request. Nothing is delivered until they confirm.","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"requestBody":{"$ref":"#/components/requestBodies/CreateReportSchedule"},"responses":{"200":{"$ref":"#/components/responses/ReportSchedule"},"400":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/report-schedules/{schedule_id}": {
      "patch":{"tags":["Reports"],"operationId":"updateReportSchedule","description":"Pause or resume. Only an active schedule can be paused and only a paused one resumed; anything else is a 409.","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/ScheduleId"}],"requestBody":{"$ref":"#/components/requestBodies/UpdateReportSchedule"},"responses":{"200":{"$ref":"#/components/responses/ReportSchedule"},"409":{"$ref":"#/components/responses/JsonError"}}},
      "delete":{"tags":["Reports"],"operationId":"deleteReportSchedule","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/ScheduleId"}],"responses":{"200":{"$ref":"#/components/responses/Deleted"},"404":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/report-schedules/{schedule_id}/verify": {"post":{"tags":["Reports"],"operationId":"resendReportVerification","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/ScheduleId"}],"responses":{"200":{"$ref":"#/components/responses/ReportSchedule"},"409":{"$ref":"#/components/responses/JsonError"}}}},
    "/report-schedules/{schedule_id}/retry": {"post":{"tags":["Reports"],"operationId":"retryReportDelivery","description":"Re-queue the last failed delivery. 404 when there is no failed report to retry.","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/ScheduleId"}],"responses":{"200":{"$ref":"#/components/responses/ReportRetried"},"404":{"$ref":"#/components/responses/JsonError"}}}},
    "/preferences/recent-ranges":{"post":{"tags":["Preferences"],"operationId":"saveRecentRange","description":"Remember one custom date range for this user. Returns the whole bounded, de-duplicated list, newest first.","security":[{"sessionAuth":[]}],"requestBody":{"$ref":"#/components/requestBodies/SaveRecentRange"},"responses":{"200":{"$ref":"#/components/responses/RecentRanges"},"400":{"$ref":"#/components/responses/JsonError"}}}},
    "/sites/{slug}/monitor": {
      "get":{"tags":["Monitoring"],"operationId":"getMonitor","description":"Monitor configuration plus bounded current status. When `configured` is false only state, region, and the three deployment fields are present.","security":[{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"}],"responses":{"200":{"$ref":"#/components/responses/Monitor"},"403":{"$ref":"#/components/responses/JsonError"}}},
      "put":{"tags":["Monitoring"],"operationId":"putMonitor","security":[{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"}],"requestBody":{"$ref":"#/components/requestBodies/MonitorConfig"},"responses":{"200":{"$ref":"#/components/responses/MonitorStatus"},"400":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/sites/{slug}/monitor/test":{"post":{"tags":["Monitoring"],"operationId":"testMonitor","description":"Probe the configured URL once, right now, and return the result without storing it. The synthetic row's `id` is the literal string \"test\".","security":[{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"}],"responses":{"200":{"$ref":"#/components/responses/MonitorProbe"},"409":{"$ref":"#/components/responses/JsonError"}}}},
    "/sites/{slug}/monitor/incidents":{"get":{"tags":["Monitoring"],"operationId":"listMonitorIncidents","description":"Newest first, at most 100 rows regardless of limit.","security":[{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"$ref":"#/components/responses/MonitorIncidentList"}}}},
    "/sites/{slug}/monitor/probes":{"get":{"tags":["Monitoring"],"operationId":"listMonitorProbes","description":"Newest first, at most 500 rows regardless of limit.","security":[{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"$ref":"#/components/responses/MonitorProbeList"}}}},
    "/push-subscriptions":{
      "get":{"tags":["Push"],"operationId":"listPushDevices","security":[{"sessionAuth":[]}],"responses":{"200":{"$ref":"#/components/responses/PushDeviceList"}}},
      "post":{"tags":["Push"],"operationId":"enrollPushDevice","description":"Body is the browser's own PushSubscription JSON plus an optional label.","security":[{"sessionAuth":[]}],"requestBody":{"$ref":"#/components/requestBodies/PushSubscription"},"responses":{"200":{"$ref":"#/components/responses/PushDevice"},"400":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/push-subscriptions/{subscription_id}":{"delete":{"tags":["Push"],"operationId":"removePushDevice","security":[{"sessionAuth":[]}],"parameters":[{"name":"subscription_id","in":"path","required":true,"description":"The `id` field of a PushDevice row.","schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/PushRemoved"},"404":{"$ref":"#/components/responses/JsonError"}}}},
    "/push-subscriptions/test":{"post":{"tags":["Push"],"operationId":"testPushDevice","security":[{"sessionAuth":[]}],"responses":{"200":{"$ref":"#/components/responses/PushQueued"},"409":{"$ref":"#/components/responses/JsonError"}}}},
    "/sites/{slug}/alerts": {
      "get":{"tags":["Alerts"],"operationId":"listSiteAlerts","parameters":[{"$ref":"#/components/parameters/Slug"}],"responses":{"200":{"$ref":"#/components/responses/AlertRuleList"}}},
      "post":{"tags":["Alerts"],"operationId":"createSiteAlert","description":"Threshold rules: traffic_drop, collection_stopped, or anomaly (baseline-day-median, both directions — same statistical method as scripts/alert_traffic.py). Delivered on the rule's channels (email and/or push, default email) to the account owner, then re-armed after a cooldown. An optional per-rule quiet_hours window (site-local hours) defers delivery of a breach without delaying when it is detected or recorded. An optional recipient_email adds one non-owner destination: it must be confirmed via a mailed /alerts/verify/<token> link (mirroring scheduled reports' consent flow) before breach mail is sent to it, and is immutable once set — delete and recreate the rule to change or remove it. See app/core/customer_alerts.py.","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"}],"requestBody":{"$ref":"#/components/requestBodies/CreateAlertRule"},"responses":{"200":{"$ref":"#/components/responses/AlertRule"},"400":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/sites/{slug}/alerts/{alert_id}": {
      "patch":{"tags":["Alerts"],"operationId":"updateSiteAlert","description":"Update enabled, channels (email/push), and/or quiet_hours_enabled+quiet_start_hour+quiet_end_hour. Thresholds are immutable once created; delete and recreate to change them.","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/AlertId"}],"requestBody":{"$ref":"#/components/requestBodies/UpdateAlertRule"},"responses":{"200":{"$ref":"#/components/responses/AlertRule"},"400":{"$ref":"#/components/responses/JsonError"}}},
      "delete":{"tags":["Alerts"],"operationId":"deleteSiteAlert","security":[{"secretKeyAuth":[]},{"sessionAuth":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/AlertId"}],"responses":{"200":{"$ref":"#/components/responses/Deleted"},"404":{"$ref":"#/components/responses/JsonError"}}}
    },
    "/sites/{slug}/alerts/deliveries":{"get":{"tags":["Alerts"],"operationId":"listSiteAlertDeliveries","description":"Newest first, at most 100 rows regardless of limit.","parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"$ref":"#/components/responses/AlertDeliveryList"}}}},
    "/retention": {"get":{"tags":["Retention"],"operationId":"getRetention","description":"IMP-0093. Effective retention by data class, account-wide, plus each visible site's own raw-event window and the shorter presets it may choose. Setting event_retention_days is not a separate endpoint -- it is a field on PATCH /sites/{slug}, same as timezone or domains.","responses":{"200":{"$ref":"#/components/responses/Retention"},"401":{"$ref":"#/components/responses/JsonError"}}}},
    "/s/{slug}/retention/preview": {"get":{"tags":["Retention"],"operationId":"previewSiteRetention","description":"Dry run: how many already-collected raw events a candidate event_retention_days would remove right now. Read-only -- issues no write.","parameters":[{"$ref":"#/components/parameters/Slug"},{"name":"event_retention_days","in":"query","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"$ref":"#/components/responses/RetentionPreview"},"400":{"$ref":"#/components/responses/JsonError"}}}}
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {"type":"http","scheme":"bearer","bearerFormat":"ctr_<key_id>.<secret> API key, read-only"},
      "secretKeyAuth": {"type":"http","scheme":"bearer","bearerFormat":"ctr_mgmt_<key_id>.<secret> secret key, full access"},
      "sessionAuth": {"type":"apiKey","in":"cookie","name":"session"}
    },
    "parameters": {
      "Slug":{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,62}$"}},
      "Range":{"name":"range","in":"query","schema":{"type":"string","enum":["today","yesterday","7d","30d","90d","6mo","12mo"],"default":"7d"}},
      "Start":{"name":"start","in":"query","schema":{"type":"string","format":"date-time"}},
      "End":{"name":"end","in":"query","schema":{"type":"string","format":"date-time"}},
      "Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":2000}},
      "Minutes":{"name":"minutes","in":"query","description":"How far back \"right now\" reaches. Clamped to 1..60.","schema":{"type":"integer","minimum":1,"maximum":60,"default":5}},
      "Interval":{"name":"interval","in":"query","required":false,"description":"Bucket width. Defaults to whatever the range implies.","schema":{"type":"string","enum":["hour","day","month"]}},
      "Compare":{"name":"compare","in":"query","required":false,"description":"Any of 1/on/previous/true adds a `comparison` block scoring the immediately preceding period of equal length.","schema":{"type":"string","enum":["1","on","previous","true"]}},
      "Bbox":{"name":"bbox","in":"query","required":false,"description":"min_lon,min_lat,max_lon,max_lat. min_lon > max_lon means the box wraps the antimeridian. A malformed box is a 400, never a silent widening to the whole world.","schema":{"type":"string"}},
      "Cursor":{"name":"cursor","in":"query","description":"Opaque cursor returned by the previous page.","schema":{"type":"string"}},
      "Dimension":{"name":"dimension","in":"path","required":true,"description":"Which dimension to rank. Note the page ranking is \"path\", not \"page\".","schema":{"$ref":"#/components/schemas/DimensionName"}},
      "FilterDimension":{"name":"filter_dimension","in":"query","description":"IMP-0124 cross-panel drill-down: scope this request to one segment. Must be given together with filter_value. Every dimension in DimensionName is accepted (IMP-0172) except on /s/{slug}/goals, which still accepts only referrer, referrer_source, country, region, city, browser, os, device.","schema":{"$ref":"#/components/schemas/DimensionName"}},
      "FilterValue":{"name":"filter_value","in":"query","description":"The segment value to scope to, e.g. \"Chrome\" for filter_dimension=browser. Must be given together with filter_dimension.","schema":{"type":"string"}},
      "ApiKeyId":{"name":"id","in":"path","required":true,"description":"The key's public identifier, exactly matching the `id` field returned by listApiKeys and createApiKey.","schema":{"type":"string"}},
      "ScheduleId":{"name":"schedule_id","in":"path","required":true,"description":"The `id` field of a ReportSchedule.","schema":{"type":"string"}},
      "AlertId":{"name":"alert_id","in":"path","required":true,"description":"The `id` field of an AlertRule.","schema":{"type":"string"}}
    },
    "requestBodies": {
      "CreateSite":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,62}$"},"name":{"type":"string"},"timezone":{"type":"string","default":"UTC","description":"IANA zone name."},"domains":{"type":"array","items":{"type":"string"}}}}}}},
      "UpdateSite":{"required":true,"description":"Every field is optional; only the ones sent are changed.","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"domains":{"type":"array","items":{"type":"string"}},"identity_mode":{"type":"string","enum":["hash","cookie_on_consent"]},"timezone":{"type":"string"},"excluded_ips":{"type":"array","items":{"type":"string"}},"excluded_paths":{"type":"array","items":{"type":"string"}},"respect_dnt":{"type":"boolean"},"public":{"type":"boolean"},"event_retention_days":{"type":["integer","null"],"description":"One of the presets getRetention reports, or null for the service default."}}}}}},
      "CreateGoal":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","type","pattern"],"properties":{"name":{"type":"string","maxLength":80},"type":{"type":"string","enum":["path","event"]},"pattern":{"type":"string","maxLength":300,"description":"A path (exact, or with a trailing * wildcard) for type=path, or a custom event name for type=event."},"value":{"type":["number","null"],"description":"A fixed monetary value per conversion. When set it overrides per-event revenue."}}}}}},
      "CreateFunnel":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","steps"],"properties":{"name":{"type":"string"},"steps":{"type":"array","minItems":2,"maxItems":5,"items":{"$ref":"#/components/schemas/FunnelStep"}}}}}}},
      "MutedInsightRules":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rules"],"properties":{"rules":{"type":"array","description":"The complete new opt-out list. Any name outside InsightRuleName is a 400.","items":{"$ref":"#/components/schemas/InsightRuleName"}}}}}}},
      "InsightFeedback":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["value"],"properties":{"value":{"type":"string","enum":["helpful","not_helpful"]},"rule":{"type":"string","description":"Optional. The finding's own `rule`, for chronic_not_helpful_rules reporting."}}}}}},
      "CreateExport":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["events","sessions"]},"range":{"type":"string","enum":["today","yesterday","7d","30d","90d","6mo","12mo"]},"start":{"type":"string","format":"date-time"},"end":{"type":"string","format":"date-time"}}}}}},
      "CreateShareLink":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["slug"],"properties":{"slug":{"type":"string"},"expires_days":{"type":["integer","string","null"],"minimum":1,"maximum":3650,"description":"Days until the link expires. null, \"\" or \"never\" means it never expires."},"password":{"type":"string","description":"Optional. Readers must enter it before the report renders."}}}}}},
      "CreateApiKey":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label"],"properties":{"label":{"type":"string","maxLength":100,"description":"Required and non-empty: the only thing that identifies this key in a later listing."},"kind":{"type":"string","enum":["read","management"],"default":"read"},"slug":{"type":"string","description":"Bind the key to one site. Omit or send \"\" for an account-wide key."},"password":{"type":"string","description":"Required when kind=management, and only a browser session may send it."}}}}}},
      "CreateReportSchedule":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["slug","recipient","frequency"],"properties":{"slug":{"type":"string"},"recipient":{"type":"string","format":"email"},"frequency":{"type":"string","enum":["daily","weekly","monthly"]},"delivery_hour":{"type":"integer","minimum":0,"maximum":23,"default":8},"weekday":{"type":"integer","minimum":0,"maximum":6,"default":0,"description":"0 = Monday. Only read for frequency=weekly."},"include_csv":{"type":"boolean","default":true}}}}}},
      "UpdateReportSchedule":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state"],"properties":{"state":{"type":"string","enum":["active","paused"]}}}}}},
      "SaveRecentRange":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["start_date","end_date"],"properties":{"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"}}}}}},
      "MonitorConfig":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"An HTTPS URL on one of the site's registered domains."},"enabled":{"type":"boolean","default":false}}}}}},
      "PushSubscription":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["endpoint","keys"],"properties":{"endpoint":{"type":"string","format":"uri","maxLength":4096},"keys":{"type":"object","required":["p256dh","auth"],"properties":{"p256dh":{"type":"string"},"auth":{"type":"string"}}},"label":{"type":"string","default":"Browser"}}}}}},
      "CreateAlertRule":{"required":true,"description":"Which threshold fields are read depends on kind; the rest are ignored. traffic_drop reads window_hours/drop_pct/min_volume, anomaly reads window_hours/baseline_days/drop_pct/spike_pct/min_volume, collection_stopped reads stale_hours.","content":{"application/json":{"schema":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["traffic_drop","collection_stopped","anomaly"]},"name":{"type":"string"},"window_hours":{"type":"integer"},"drop_pct":{"type":"number"},"min_volume":{"type":"integer"},"stale_hours":{"type":"integer"},"baseline_days":{"type":"integer"},"spike_pct":{"type":"number"},"channels":{"type":"array","items":{"type":"string","enum":["email","push"]},"default":["email"]},"quiet_hours_enabled":{"type":"boolean","default":false},"quiet_start_hour":{"type":["integer","null"],"minimum":0,"maximum":23},"quiet_end_hour":{"type":["integer","null"],"minimum":0,"maximum":23},"recipient_email":{"type":["string","null"],"format":"email","description":"IMP-0166. One extra destination, immutable once set, and mailed nothing until it confirms."}}}}}},
      "UpdateAlertRule":{"required":true,"description":"Every field is optional; thresholds cannot be changed at all.","content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"},"channels":{"type":"array","items":{"type":"string","enum":["email","push"]}},"quiet_hours_enabled":{"type":"boolean"},"quiet_start_hour":{"type":["integer","null"],"minimum":0,"maximum":23},"quiet_end_hour":{"type":["integer","null"],"minimum":0,"maximum":23}}}}}}
    },
    "schemas": {
      "DimensionName":{"type":"string","description":"Every dimension app/core/query.py's DIMENSIONS map accepts.","enum":["path","entry_page","exit_page","referrer","referrer_source","country","region","city","browser","os","device","screen","language","locale","utm_source","utm_medium","utm_campaign"]},
      "Success":{"type":"object","required":["ok","data"],"properties":{"ok":{"const":true},"data":{}}},
      "Error":{"type":"object","required":["ok","error"],"properties":{"ok":{"const":false},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}},

      "Timestamp":{"type":["string","null"],"description":"ISO-8601 with an explicit UTC offset, e.g. 2026-08-02T03:36:16.056000+00:00."},
      "HttpDate":{"type":["string","null"],"description":"RFC 1123 HTTP-date in UTC, e.g. \"Sun, 02 Aug 2026 03:35:15 GMT\". This is Flask's default encoding for a raw stored timestamp and is NOT ISO-8601 -- only the per-visit reads return it."},

      "Site":{"type":"object","required":["slug","name","domains","identity_mode","public"],"properties":{"slug":{"type":"string"},"name":{"type":"string"},"domains":{"type":"array","items":{"type":"string"}},"identity_mode":{"type":["string","null"],"enum":["hash","cookie_on_consent",null]},"public":{"type":"boolean"}}},
      "SiteCreated":{"type":"object","required":["slug","tracking_id"],"properties":{"slug":{"type":"string"},"tracking_id":{"type":"string"}}},
      "SiteUpdated":{"type":"object","required":["slug","updated"],"properties":{"slug":{"type":"string"},"updated":{"const":true}}},
      "SiteDeletionScheduled":{"type":"object","required":["slug","delete_after"],"properties":{"slug":{"type":"string"},"delete_after":{"type":"string","description":"ISO-8601. The site is recoverable with restoreSite until this moment."}}},
      "SiteRestored":{"type":"object","required":["slug","restored"],"properties":{"slug":{"type":"string"},"restored":{"const":true}}},
      "SiteValidation":{"type":"object","required":["installed","last_seen","source_quality","domains","snippet"],"properties":{"installed":{"type":"boolean","description":"True when an event arrived in the last 24 hours."},"last_seen":{"type":["string","null"],"description":"ISO-8601 in UTC, without an offset suffix."},"source_quality":{"type":["string","null"]},"domains":{"type":"array","items":{"type":"string"}},"snippet":{"type":"string","description":"The <script> tag to paste into the site."}}},
      "FreshnessObjective":{"type":"object","required":["available","window_hours","target_percent"],"properties":{"available":{"type":"boolean","description":"False when the aggregation could not run; the measured fields are then absent."},"window_hours":{"type":"integer"},"target_percent":{"type":"number"},"covered_hours":{"type":"integer"},"actual_percent":{"type":"number"},"meets_target":{"type":"boolean"},"computed_at":{"$ref":"#/components/schemas/Timestamp"}}},
      "SiteCollectionHealth":{"type":"object","required":["last_event_at","seconds_since_last_event","network"],"properties":{"last_event_at":{"$ref":"#/components/schemas/Timestamp"},"seconds_since_last_event":{"type":["integer","null"]},"network":{"type":"object","required":["last_event_at","seconds_since_last_event","freshness_objective"],"properties":{"last_event_at":{"$ref":"#/components/schemas/Timestamp"},"seconds_since_last_event":{"type":["integer","null"]},"freshness_objective":{"$ref":"#/components/schemas/FreshnessObjective"}}}}},
      "MeasurementKey":{"type":"object","required":["key","slug","shown_once","header"],"properties":{"key":{"type":"string","description":"The only copy. Never returned again."},"slug":{"type":"string"},"shown_once":{"const":true},"header":{"type":"string","description":"The request header to send it in."}}},
      "IngestTelemetry":{"type":"object","required":["available","recent_write_failure"],"properties":{"available":{"type":"boolean"},"recent_write_failure":{"type":"boolean"}},"additionalProperties":{"type":"number","description":"Deployment-wide ingest counters, open-ended by design: whichever counters the ingest path has written. Integer counts, or a Unix timestamp for a last_* key."}},

      "Metrics":{"type":"object","required":["pageviews","visitors","sessions","bounce_rate","avg_duration_seconds","views_per_session"],"properties":{"pageviews":{"type":"integer"},"visitors":{"type":"integer"},"sessions":{"type":"integer"},"bounce_rate":{"type":"number","description":"Percent of sessions with exactly one pageview."},"avg_duration_seconds":{"type":"integer","description":"Averaged over sessions with a measurable duration only."},"views_per_session":{"type":"number"}}},
      "MetricsChangePct":{"type":"object","description":"Percent change of each Metrics field against the previous period. null where the baseline was zero -- the UI shows a dash, never \"+100%\".","required":["pageviews","visitors","sessions","bounce_rate","avg_duration_seconds","views_per_session"],"additionalProperties":{"type":["number","null"]},"properties":{"pageviews":{"type":["number","null"]},"visitors":{"type":["number","null"]},"sessions":{"type":["number","null"]},"bounce_rate":{"type":["number","null"]},"avg_duration_seconds":{"type":["number","null"]},"views_per_session":{"type":["number","null"]}}},
      "NewVsReturning":{"type":"object","description":"BUG-0026. Only visitors whose id is stable across days can be classified at all; `applicable` false with traffic present means this site's ids cannot answer the question, which is not the same statement as \"nobody returned\".","required":["new","returning","unclassifiable","applicable","reason"],"properties":{"new":{"type":"integer"},"returning":{"type":"integer"},"unclassifiable":{"type":"integer"},"applicable":{"type":"boolean"},"reason":{"type":["string","null"]}}},
      "Overview":{"type":"object","required":["current","previous","change_pct","new_vs_returning","identity_mode"],"properties":{"current":{"$ref":"#/components/schemas/Metrics"},"previous":{"$ref":"#/components/schemas/Metrics","description":"The immediately preceding period of equal length. Always present -- no compare flag needed."},"change_pct":{"$ref":"#/components/schemas/MetricsChangePct"},"new_vs_returning":{"$ref":"#/components/schemas/NewVsReturning"},"identity_mode":{"type":["string","null"],"enum":["hash","cookie_on_consent",null]}}},
      "TimeseriesPoint":{"type":"object","required":["period","pageviews","visitors","sessions"],"properties":{"period":{"type":"string","description":"Bucket label in the response's own `timezone`: %Y-%m-%dT%H for hour, %Y-%m-%d for day, %Y-%m for month."},"pageviews":{"type":"integer"},"visitors":{"type":"integer"},"sessions":{"type":"integer"}}},
      "TimeseriesComparison":{"type":"object","required":["kind","start","end","points"],"properties":{"kind":{"const":"previous_period"},"start":{"type":"string","format":"date-time"},"end":{"type":"string","format":"date-time"},"points":{"type":"array","items":{"$ref":"#/components/schemas/TimeseriesPoint"}}}},
      "Timeseries":{"type":"object","required":["interval","timezone","points"],"properties":{"interval":{"type":"string","enum":["hour","day","month"]},"timezone":{"type":"string","description":"IMP-0122. The one zone this whole response was cut and labelled in."},"points":{"type":"array","items":{"$ref":"#/components/schemas/TimeseriesPoint"}},"comparison":{"$ref":"#/components/schemas/TimeseriesComparison","description":"Present only with compare."}}},
      "DimensionRow":{"type":"object","required":["value","visitors"],"properties":{"value":{"type":"string"},"visitors":{"type":"integer"},"pageviews":{"type":"integer","description":"Event-backed dimensions only."},"sessions":{"type":"integer","description":"entry_page and exit_page only, in place of pageviews."}}},
      "DimensionRanking":{"type":"object","required":["dimension","rows"],"properties":{"dimension":{"$ref":"#/components/schemas/DimensionName"},"rows":{"type":"array","items":{"$ref":"#/components/schemas/DimensionRow"}}}},
      "GeoPoint":{"type":"object","required":["city","region","country","lat","lon","visitors","pageviews"],"properties":{"city":{"type":["string","null"]},"region":{"type":["string","null"]},"country":{"type":["string","null"],"description":"ISO 3166-1 alpha-2."},"lat":{"type":"number","description":"The city centroid, never a street address -- see app/core/geoip.py."},"lon":{"type":"number"},"visitors":{"type":"integer"},"pageviews":{"type":"integer"}}},
      "SiteGeo":{"type":"object","required":["points","countries","truncated"],"properties":{"points":{"type":"array","items":{"$ref":"#/components/schemas/GeoPoint"}},"countries":{"type":"array","items":{"$ref":"#/components/schemas/DimensionRow"},"description":"Whole-world country ranking, top 100, unaffected by bbox."},"truncated":{"type":"boolean","description":"True when the point list hit `limit` and more cities exist."}}},
      "RealtimeFeedEntry":{"type":"object","required":["ts","type"],"properties":{"ts":{"$ref":"#/components/schemas/HttpDate"},"type":{"type":"string","enum":["pageview","event"]},"path":{"type":["string","null"]},"country":{"type":["string","null"]},"city":{"type":["string","null"]},"browser":{"type":["string","null"]},"os":{"type":["string","null"]},"device_type":{"type":["string","null"]},"event_name":{"type":["string","null"]},"referrer_name":{"type":["string","null"]}}},
      "Realtime":{"type":"object","required":["active_visitors","window_minutes","top_pages","feed","points"],"properties":{"active_visitors":{"type":"integer"},"window_minutes":{"type":"integer"},"top_pages":{"type":"array","items":{"type":"object","required":["path","views"],"properties":{"path":{"type":["string","null"]},"views":{"type":"integer"}}}},"feed":{"type":"array","items":{"$ref":"#/components/schemas/RealtimeFeedEntry"}},"points":{"type":"array","items":{"type":"object","required":["lat","lon","city","n"],"properties":{"lat":{"type":"number"},"lon":{"type":"number"},"city":{"type":["string","null"]},"n":{"type":"integer"}}}}}},
      "VisitorSession":{"type":"object","description":"The stored session document, minus site_id and the TTL field. Its id is `_id`, and its timestamps are HttpDate rather than ISO-8601.","required":["_id","visitor_id","started_at","last_seen"],"properties":{"_id":{"type":"string"},"visitor_id":{"type":"string"},"started_at":{"$ref":"#/components/schemas/HttpDate"},"last_seen":{"$ref":"#/components/schemas/HttpDate"},"pageviews":{"type":"integer"},"events":{"type":"integer"},"entry_path":{"type":["string","null"]},"exit_path":{"type":["string","null"]},"referrer":{"type":["string","null"]},"referrer_source":{"type":["string","null"]},"referrer_name":{"type":["string","null"]},"country":{"type":["string","null"]},"region":{"type":["string","null"]},"city":{"type":["string","null"]},"lat":{"type":["number","null"]},"lon":{"type":["number","null"]},"device_type":{"type":["string","null"]},"browser":{"type":["string","null"]},"os":{"type":["string","null"]},"screen":{"type":["string","null"]},"language":{"type":["string","null"]},"id_mode":{"type":["string","null"]},"utm_source":{"type":["string","null"]},"utm_medium":{"type":["string","null"]},"utm_campaign":{"type":["string","null"]}}},
      "VisitorSessionPage":{"type":"object","required":["sessions","next_cursor","has_more"],"properties":{"sessions":{"type":"array","items":{"$ref":"#/components/schemas/VisitorSession"}},"next_cursor":{"type":["string","null"],"description":"Send back unchanged as `cursor`. Opaque -- never decode or synthesize one."},"has_more":{"type":"boolean"}}},
      "SessionHit":{"type":"object","required":["ts","type"],"properties":{"ts":{"$ref":"#/components/schemas/HttpDate"},"type":{"type":"string","enum":["pageview","event"]},"path":{"type":["string","null"]},"title":{"type":["string","null"]},"event_name":{"type":["string","null"]},"props":{"type":["object","null"]},"duration_ms":{"type":["integer","null"]},"id_mode":{"type":["string","null"]}}},
      "VisitorSessionDetail":{"allOf":[{"$ref":"#/components/schemas/VisitorSession"},{"type":"object","required":["site_id","hits"],"properties":{"site_id":{"type":"string"},"hits":{"type":"array","items":{"$ref":"#/components/schemas/SessionHit"},"description":"Every hit of the session in arrival order."}}}]},
      "VisitorMapPoint":{"type":"object","required":["session","at","lat","lon","visitors"],"properties":{"session":{"type":"string"},"at":{"$ref":"#/components/schemas/HttpDate"},"lat":{"type":"number"},"lon":{"type":"number"},"city":{"type":["string","null"]},"region":{"type":["string","null"]},"country":{"type":["string","null"]},"entry_path":{"type":["string","null"]},"exit_path":{"type":["string","null"]},"pageviews":{"type":"integer"},"visitors":{"const":1,"description":"Always 1 -- one pin is one visit."},"browser":{"type":["string","null"]},"os":{"type":["string","null"]},"device_type":{"type":["string","null"]}}},
      "VisitorMapPage":{"type":"object","required":["points","total","skip","limit","next_cursor","has_more"],"properties":{"points":{"type":"array","items":{"$ref":"#/components/schemas/VisitorMapPoint"}},"total":{"type":"integer","description":"Locatable visits in the range, for \"page 1 of N\"."},"skip":{"type":["integer","null"],"description":"null on a cursor-paginated request; only the legacy skip mode sets it."},"limit":{"type":"integer"},"next_cursor":{"type":["string","null"]},"has_more":{"type":"boolean"}}},
      "CustomEvent":{"type":"object","required":["name","count","visitors","revenue"],"properties":{"name":{"type":["string","null"]},"count":{"type":"integer"},"visitors":{"type":"integer"},"revenue":{"type":"number"}}},

      "NetworkSiteRow":{"type":"object","required":["slug","name","domain","pageviews","visitors","sessions","bounce_rate","active_now"],"properties":{"slug":{"type":"string"},"name":{"type":"string"},"domain":{"type":["string","null"],"description":"The site's first registered domain."},"pageviews":{"type":"integer"},"visitors":{"type":"integer"},"sessions":{"type":"integer"},"bounce_rate":{"type":"number"},"active_now":{"type":"integer"}}},
      "NetworkTotals":{"type":"object","required":["pageviews","visitors","sessions","site_count","sites_with_traffic"],"properties":{"pageviews":{"type":"integer"},"visitors":{"type":"integer"},"sessions":{"type":"integer"},"site_count":{"type":"integer"},"sites_with_traffic":{"type":"integer"},"active_now":{"type":"integer","description":"Present on `totals`, absent from a comparison's totals."}}},
      "NetworkOverview":{"type":"object","required":["sites","totals","interval","timezone","points"],"properties":{"sites":{"type":"array","items":{"$ref":"#/components/schemas/NetworkSiteRow"},"description":"Busiest first. A site with no traffic is a zero row, not a missing one."},"totals":{"$ref":"#/components/schemas/NetworkTotals"},"interval":{"type":"string","enum":["hour","day","month"]},"timezone":{"type":"string"},"points":{"type":"array","items":{"$ref":"#/components/schemas/TimeseriesPoint"},"description":"Network chart. Reports pageviews only -- visitors and sessions are 0 by construction, because distinct counts cannot be summed across sites."},"comparison":{"type":"object","description":"Present only with compare.","required":["kind","start","end","totals","change_pct","points"],"properties":{"kind":{"const":"previous_period"},"start":{"type":"string","format":"date-time"},"end":{"type":"string","format":"date-time"},"totals":{"$ref":"#/components/schemas/NetworkTotals"},"change_pct":{"type":"object","additionalProperties":{"type":["number","null"]}},"points":{"type":"array","items":{"$ref":"#/components/schemas/TimeseriesPoint"}}}}}},
      "NetworkGeoPoint":{"allOf":[{"$ref":"#/components/schemas/GeoPoint"},{"type":"object","required":["sites"],"properties":{"sites":{"type":"array","description":"Per-site breakdown of this city. `id` indexes the roster beside the points, which is sent once rather than repeated on every dot.","items":{"type":"object","required":["id","visitors","pageviews"],"properties":{"id":{"type":"string"},"visitors":{"type":"integer"},"pageviews":{"type":"integer"}}}}}}]},
      "NetworkGeo":{"type":"object","required":["points","sites","site_count","truncated"],"properties":{"points":{"type":"array","items":{"$ref":"#/components/schemas/NetworkGeoPoint"}},"sites":{"type":"array","items":{"type":"object","required":["id","slug","name"],"properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"}}}},"site_count":{"type":"integer"},"truncated":{"type":"boolean"}}},
      "NetworkRealtime":{"type":"object","required":["sites","total"],"properties":{"sites":{"type":"object","description":"Active visitors keyed by site slug.","additionalProperties":{"type":"integer"}},"total":{"type":"integer"}}},

      "Goal":{"type":"object","required":["name","type","pattern"],"properties":{"name":{"type":"string"},"type":{"type":"string","enum":["path","event"]},"pattern":{"type":"string"}}},
      "GoalConversion":{"allOf":[{"$ref":"#/components/schemas/Goal"},{"type":"object","required":["conversions","visitors","conversion_rate","revenue"],"properties":{"conversions":{"type":"integer","description":"Converting sessions -- one per session, however many times it fired."},"visitors":{"type":"integer"},"conversion_rate":{"type":"number","description":"Percent of the period's sessions."},"revenue":{"type":"number"}}}]},
      "GoalDetail":{"type":"object","required":["name","interval","trend","landing_pages","campaigns"],"properties":{"name":{"type":"string"},"interval":{"type":"string","enum":["hour","day","month"]},"trend":{"type":"array","items":{"type":"object","required":["period","conversions"],"properties":{"period":{"type":"string"},"conversions":{"type":"integer"}}}},"landing_pages":{"type":"array","items":{"type":"object","required":["value","conversions"],"properties":{"value":{"type":"string"},"conversions":{"type":"integer"}}}},"campaigns":{"type":"array","description":"IMP-0086. First-touch UTM attribution. Sessions with no UTM parameters at all are excluded rather than reported as a \"none\" row.","items":{"type":"object","required":["value","source","medium","campaign","conversions"],"properties":{"value":{"type":"string","description":"source / medium / campaign, blank parts omitted."},"source":{"type":["string","null"]},"medium":{"type":["string","null"]},"campaign":{"type":["string","null"]},"conversions":{"type":"integer"}}}}}},
      "FunnelStep":{"type":"object","required":["name","type","pattern"],"properties":{"name":{"type":"string"},"type":{"type":"string","enum":["path","event"]},"pattern":{"type":"string"}}},
      "Funnel":{"type":"object","required":["name","steps"],"properties":{"name":{"type":"string"},"steps":{"type":"array","items":{"$ref":"#/components/schemas/FunnelStep"}}}},
      "FunnelReportStep":{"type":"object","required":["value","type","pattern","count","drop_off","drop_off_rate","conversion_rate","conversion_rate_ci"],"properties":{"value":{"type":"string","description":"The step's label. Named `value`, not `name`, unlike the definition's steps."},"type":{"type":"string","enum":["path","event"]},"pattern":{"type":"string"},"count":{"type":"integer","description":"Sessions that reached AT LEAST this step, in order."},"drop_off":{"type":"integer","description":"Sessions lost since the previous step. 0 on step 1."},"drop_off_rate":{"type":"number"},"conversion_rate":{"type":"number","description":"Percent of the funnel's own entrants, not of the site's sessions."},"conversion_rate_ci":{"type":"object","required":["low","high"],"description":"IMP-0175 Wilson score interval over the same numerator and denominator.","properties":{"low":{"type":"number"},"high":{"type":"number"}}},"previous_count":{"type":"integer","description":"compare only."},"previous_conversion_rate":{"type":"number","description":"compare only."},"conversion_rate_change_pct":{"type":["number","null"],"description":"compare only. null against a zero baseline."}}},
      "FunnelReport":{"type":"object","required":["name","entered","steps","sample_size_floor","low_sample"],"properties":{"name":{"type":"string"},"entered":{"type":"integer"},"steps":{"type":"array","items":{"$ref":"#/components/schemas/FunnelReportStep"}},"sample_size_floor":{"type":"integer"},"low_sample":{"type":"boolean"},"previous_entered":{"type":"integer","description":"compare only."},"previous_low_sample":{"type":"boolean","description":"compare only."}}},
      "Insight":{"type":"object","required":["fingerprint","rule","metric","subject","direction","magnitude","confidence","title","narrative","drilldown_link","evidence"],"properties":{"fingerprint":{"type":"string","description":"A hash of the finding's content, not a stored id. Pass it to dismissInsight/feedbackInsight."},"rule":{"type":"string","description":"traffic_change, goal_conversion_shift, referrer_change, page_change, bounce_rate_change, or two of those joined by '+' for a combined card."},"metric":{"type":"string"},"subject":{"type":"string"},"direction":{"type":"string"},"magnitude":{"type":["number","null"],"description":"How far it moved, in the metric's own units. null where there is no meaningful ratio -- a goal that recorded its first conversions, or stopped converting entirely."},"confidence":{"type":"string","enum":["low","medium","high"]},"title":{"type":"string"},"narrative":{"type":"string","description":"English prose quoting the real numbers. Never model-generated."},"drilldown_link":{"type":"string","description":"A dashboard path, already carrying the segment filter where the tab supports one."},"evidence":{"type":"object","description":"The numbers the narrative quotes. Keys vary by rule and are deliberately not fixed here."}}},
      "InsightRuleName":{"type":"string","description":"Every rule app/core/insights.py's MUTABLE_RULES allows an account to opt out of. The last entry is the combined card a co-occurring traffic and bounce-rate finding folds into (IMP-0207).","enum":["traffic_change","goal_conversion_shift","referrer_change","page_change","bounce_rate_change","traffic_change+bounce_rate_change"]},
      "InsightFeedbackCandidate":{"type":"object","required":["rule","helpful","not_helpful","total","not_helpful_ratio"],"properties":{"rule":{"type":"string"},"helpful":{"type":"integer"},"not_helpful":{"type":"integer"},"total":{"type":"integer"},"not_helpful_ratio":{"type":"number"}}},

      "ExportJob":{"type":"object","required":["id","site_id","kind","state","range_start","range_end","created_at","started_at","finished_at","expires_at","row_count","size_bytes","checksum_sha256","last_error","attempts"],"properties":{"id":{"type":"string"},"site_id":{"type":"string"},"kind":{"type":"string","enum":["events","sessions"]},"state":{"type":"string","enum":["pending","retry","processing","done","failed","cancelled","expired"]},"range_start":{"$ref":"#/components/schemas/Timestamp"},"range_end":{"$ref":"#/components/schemas/Timestamp"},"created_at":{"$ref":"#/components/schemas/Timestamp"},"started_at":{"$ref":"#/components/schemas/Timestamp"},"finished_at":{"$ref":"#/components/schemas/Timestamp"},"expires_at":{"$ref":"#/components/schemas/Timestamp"},"row_count":{"type":["integer","null"]},"size_bytes":{"type":["integer","null"]},"checksum_sha256":{"type":["string","null"]},"last_error":{"type":["string","null"]},"attempts":{"type":"integer"}}},
      "ShareLink":{"type":"object","required":["id","slug","site_name","created_at","expires_at","has_password"],"properties":{"id":{"type":"string"},"slug":{"type":["string","null"]},"site_name":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/Timestamp"},"expires_at":{"$ref":"#/components/schemas/Timestamp"},"has_password":{"type":"boolean"}}},
      "ShareLinkCreated":{"type":"object","required":["url","slug","expires_days","has_password","shown_once"],"properties":{"url":{"type":"string","description":"The only copy of the working URL. The listing never returns the token."},"slug":{"type":"string"},"expires_days":{"type":["integer","null"]},"has_password":{"type":"boolean"},"shown_once":{"const":true}}},
      "ApiKey":{"type":"object","required":["id","label","slug","site_name","scope","kind","created_at","last_used_at"],"properties":{"id":{"type":"string","description":"The public identifier, e.g. ctr_xxx or ctr_mgmt_xxx. This is the value the deleteApiKey {id} path parameter takes."},"label":{"type":"string"},"slug":{"type":["string","null"],"description":"null for an account-wide key."},"site_name":{"type":["string","null"]},"scope":{"type":"string","enum":["account","site"]},"kind":{"type":"string","enum":["read","management"]},"created_at":{"$ref":"#/components/schemas/Timestamp"},"last_used_at":{"$ref":"#/components/schemas/Timestamp"}}},
      "ApiKeyCreated":{"type":"object","required":["key","id","label","slug","scope","kind","shown_once"],"properties":{"key":{"type":"string","description":"The full credential, key_id and secret joined by a dot. Only the secret's hash is stored; this is the only time it is ever returned."},"id":{"type":"string","description":"The public half alone, matching ApiKey.id in later listings."},"label":{"type":"string"},"slug":{"type":["string","null"]},"scope":{"type":"string","enum":["account","site"]},"kind":{"type":"string","enum":["read","management"]},"shown_once":{"const":true}}},
      "ReportSchedule":{"type":"object","required":["id","site_id","site_name","slug","recipient","frequency","delivery_hour","weekday","timezone","include_csv","state","next_run_at","last_delivered_at","last_error"],"properties":{"id":{"type":"string"},"site_id":{"type":"string"},"site_name":{"type":["string","null"]},"slug":{"type":["string","null"]},"recipient":{"type":"string","format":"email"},"frequency":{"type":"string","enum":["daily","weekly","monthly"]},"delivery_hour":{"type":"integer","minimum":0,"maximum":23},"weekday":{"type":"integer","minimum":0,"maximum":6},"timezone":{"type":"string"},"include_csv":{"type":"boolean"},"state":{"type":"string","enum":["pending_verification","active","paused","unsubscribed","revoked"]},"next_run_at":{"$ref":"#/components/schemas/Timestamp"},"last_delivered_at":{"$ref":"#/components/schemas/Timestamp"},"last_error":{"type":["string","null"]}}},
      "AlertRule":{"type":"object","description":"Which threshold fields are present depends on `kind`: traffic_drop carries window_hours/drop_pct/min_volume, anomaly carries those plus baseline_days/spike_pct, collection_stopped carries stale_hours.","required":["id","site_id","kind","name","enabled","channels","quiet_hours_enabled","quiet_start_hour","quiet_end_hour","recipient_email","recipient_state","created_at","last_evaluated_at","last_state"],"properties":{"id":{"type":"string"},"site_id":{"type":"string"},"kind":{"type":"string","enum":["traffic_drop","collection_stopped","anomaly"]},"name":{"type":["string","null"]},"enabled":{"type":"boolean"},"channels":{"type":"array","items":{"type":"string","enum":["email","push"]}},"quiet_hours_enabled":{"type":"boolean"},"quiet_start_hour":{"type":["integer","null"]},"quiet_end_hour":{"type":["integer","null"]},"recipient_email":{"type":["string","null"]},"recipient_state":{"type":["string","null"],"description":"IMP-0166. pending until the extra recipient confirms."},"created_at":{"$ref":"#/components/schemas/Timestamp"},"last_evaluated_at":{"$ref":"#/components/schemas/Timestamp"},"last_state":{"type":["string","null"]},"window_hours":{"type":"integer"},"drop_pct":{"type":"number"},"min_volume":{"type":"integer"},"spike_pct":{"type":"number"},"baseline_days":{"type":"integer"},"stale_hours":{"type":"integer"}}},
      "AlertDelivery":{"type":"object","required":["id","alert_id","kind","channels","channels_state","state","deferred_for_quiet_hours","created_at","finished_at","last_error"],"properties":{"id":{"type":"string"},"alert_id":{"type":["string","null"]},"kind":{"type":["string","null"]},"channels":{"type":"array","items":{"type":"string"}},"channels_state":{"type":"object","description":"Per-channel delivery outcome, keyed by channel name.","additionalProperties":{"type":"object"}},"state":{"type":["string","null"]},"deferred_for_quiet_hours":{"type":"boolean"},"created_at":{"$ref":"#/components/schemas/Timestamp"},"finished_at":{"$ref":"#/components/schemas/Timestamp"},"last_error":{"type":["string","null"]}}},

      "MonitorStatus":{"type":"object","description":"Everything below `configured` is absent while no monitor exists.","required":["configured","state","region"],"properties":{"configured":{"type":"boolean"},"state":{"type":"string"},"region":{"type":"string","description":"A label, not a resolved address. Checks run from one Canadian region."},"url":{"type":["string","null"]},"enabled":{"type":"boolean"},"last_check":{"$ref":"#/components/schemas/Timestamp"},"latency_ms":{"type":["integer","null"]},"http_status":{"type":["integer","null"]},"error_category":{"type":["string","null"]},"uptime_percentage":{"type":["number","null"],"description":"Over the last 30 days, or null with no probes."},"probe_count_30d":{"type":"integer"},"transition_suppressed":{"type":"boolean"}}},
      "Monitor":{"allOf":[{"$ref":"#/components/schemas/MonitorStatus"},{"type":"object","required":["controls_available","engine_enabled","disclosure"],"properties":{"controls_available":{"type":"boolean"},"engine_enabled":{"type":"boolean"},"disclosure":{"type":"string","description":"The single-region caveat, meant to be shown verbatim."}}}]},
      "MonitorProbe":{"type":"object","required":["id","checked_at","healthy","latency_ms","http_status","error_category","region"],"properties":{"id":{"type":"string"},"checked_at":{"$ref":"#/components/schemas/Timestamp"},"healthy":{"type":"boolean"},"latency_ms":{"type":["integer","null"]},"http_status":{"type":["integer","null"]},"error_category":{"type":["string","null"]},"region":{"type":"string"}}},
      "MonitorIncident":{"type":"object","required":["id","opened_at","closed_at","duration_seconds","error_category","http_status","region","email_suppressed"],"properties":{"id":{"type":"string"},"opened_at":{"$ref":"#/components/schemas/Timestamp"},"closed_at":{"$ref":"#/components/schemas/Timestamp"},"duration_seconds":{"type":["integer","null"]},"error_category":{"type":["string","null"]},"http_status":{"type":["integer","null"]},"region":{"type":"string"},"email_suppressed":{"type":"boolean"}}},
      "PushDevice":{"type":"object","required":["id","label","state","created_at","last_delivered_at"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"state":{"type":"string"},"created_at":{"$ref":"#/components/schemas/Timestamp"},"last_delivered_at":{"$ref":"#/components/schemas/Timestamp"}}},

      "RetentionDataClass":{"type":"object","required":["id","label","description","days","configurable"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"days":{"type":["integer","null"],"description":"null means kept indefinitely."},"configurable":{"type":"boolean"},"configurable_note":{"type":"string"}}},
      "RetentionSiteRow":{"type":"object","required":["slug","name","event_retention_days","event_retention_days_override","event_retention_is_custom","next_event_purge_at"],"properties":{"slug":{"type":"string"},"name":{"type":"string"},"event_retention_days":{"type":"integer","description":"Effective window: the site's own choice, or the service default."},"event_retention_days_override":{"type":["integer","null"]},"event_retention_is_custom":{"type":"boolean"},"next_event_purge_at":{"$ref":"#/components/schemas/Timestamp"}}},
      "Retention":{"type":"object","required":["data_classes","sites","available_event_retention_presets"],"properties":{"data_classes":{"type":"array","items":{"$ref":"#/components/schemas/RetentionDataClass"}},"sites":{"type":"array","items":{"$ref":"#/components/schemas/RetentionSiteRow"}},"available_event_retention_presets":{"type":"array","items":{"type":"integer"}}}},
      "RetentionPreview":{"type":"object","required":["site_id","current_event_retention_days","candidate_event_retention_days","candidate_cutoff","would_delete_now"],"properties":{"site_id":{"type":"string"},"current_event_retention_days":{"type":"integer"},"candidate_event_retention_days":{"type":"integer"},"candidate_cutoff":{"type":"string"},"would_delete_now":{"type":"integer"}}},

      "RecentRange":{"type":"object","required":["start_date","end_date"],"properties":{"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"}}}
    },
    "responses": {
      "JsonError":{"description":"Stable error envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},

      "Deleted":{"description":"The name or id of the resource that was deleted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"string"}}}}}]}}}},
      "Revoked":{"description":"The id of the credential that was revoked","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["revoked"],"properties":{"revoked":{"type":"string"}}}}}]}}}},

      "SiteList":{"description":"Every site the credential can see","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Site"}}}}]}}}},
      "SiteCreated":{"description":"The new site's slug and tracking id","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SiteCreated"}}}]}}}},
      "SiteUpdated":{"description":"Acknowledgement of the update","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SiteUpdated"}}}]}}}},
      "SiteDeletionScheduled":{"description":"When the site stops being recoverable","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SiteDeletionScheduled"}}}]}}}},
      "SiteRestored":{"description":"Acknowledgement of the restore","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SiteRestored"}}}]}}}},
      "SiteValidation":{"description":"Installation evidence, without event payloads","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SiteValidation"}}}]}}}},
      "SiteCollectionHealth":{"description":"Ingestion freshness for this site against the network objective","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SiteCollectionHealth"}}}]}}}},
      "MeasurementKey":{"description":"The rotated ingest key, shown once","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MeasurementKey"}}}]}}}},
      "IngestTelemetry":{"description":"Deployment-wide ingest counters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/IngestTelemetry"}}}]}}}},

      "NetworkOverview":{"description":"Every visible site's headline numbers plus the network total","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/NetworkOverview"}}}]}}}},
      "NetworkGeo":{"description":"Visitor locations across every visible site","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/NetworkGeo"}}}]}}}},
      "NetworkRealtime":{"description":"Active visitors per site","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/NetworkRealtime"}}}]}}}},

      "Overview":{"description":"Headline metrics, the previous period, and the new/returning split","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Overview"}}}]}}}},
      "Timeseries":{"description":"Bucketed traffic for the period","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Timeseries"}}}]}}}},
      "DimensionRanking":{"description":"Ranked values for one dimension","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/DimensionRanking"}}}]}}}},
      "SiteGeo":{"description":"City dots plus the country ranking","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SiteGeo"}}}]}}}},
      "Realtime":{"description":"Who is on the site right now","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Realtime"}}}]}}}},
      "VisitorSessionPage":{"description":"One cursor-paginated page of sessions","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/VisitorSessionPage"}}}]}}}},
      "VisitorMapPage":{"description":"One page of individual visit pins","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/VisitorMapPage"}}}]}}}},
      "VisitorSessionDetail":{"description":"One session and its ordered hits","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/VisitorSessionDetail"}}}]}}}},
      "CustomEventList":{"description":"Custom event counts with revenue where present","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["events"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/CustomEvent"}}}}}}]}}}},

      "GoalConversionList":{"description":"Per-goal conversion counts for the period","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["goals"],"properties":{"goals":{"type":"array","items":{"$ref":"#/components/schemas/GoalConversion"}}}}}}]}}}},
      "GoalCreated":{"description":"The stored goal definition","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Goal"}}}]}}}},
      "GoalDetail":{"description":"One goal's trend, landing pages, and campaigns","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/GoalDetail"}}}]}}}},
      "FunnelList":{"description":"Funnel definitions","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["funnels"],"properties":{"funnels":{"type":"array","items":{"$ref":"#/components/schemas/Funnel"}}}}}}]}}}},
      "Funnel":{"description":"The stored funnel definition","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Funnel"}}}]}}}},
      "FunnelReport":{"description":"Per-step counts and drop-off for the period","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FunnelReport"}}}]}}}},

      "InsightList":{"description":"Deterministic findings for the period","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["insights"],"properties":{"insights":{"type":"array","items":{"$ref":"#/components/schemas/Insight"}}}}}}]}}}},
      "InsightDismissed":{"description":"The fingerprint that was hidden","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["dismissed"],"properties":{"dismissed":{"type":"string"}}}}}]}}}},
      "InsightFeedback":{"description":"The fingerprint and the vote recorded","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["feedback","value"],"properties":{"feedback":{"type":"string"},"value":{"type":"string","enum":["helpful","not_helpful"]}}}}}]}}}},
      "MutedInsightRules":{"description":"The account's opt-outs and the full mutable set","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["muted_rules","available_rules"],"properties":{"muted_rules":{"type":"array","items":{"$ref":"#/components/schemas/InsightRuleName"}},"available_rules":{"type":"array","items":{"$ref":"#/components/schemas/InsightRuleName"}}}}}}]}}}},
      "MutedInsightRulesUpdated":{"description":"The stored opt-out list after the replacement","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["muted_rules"],"properties":{"muted_rules":{"type":"array","items":{"$ref":"#/components/schemas/InsightRuleName"}}}}}}]}}}},
      "InsightFeedbackSummary":{"description":"Rules rated persistently unhelpful","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["candidates"],"properties":{"candidates":{"type":"array","items":{"$ref":"#/components/schemas/InsightFeedbackCandidate"}}}}}}]}}}},

      "ExportJobList":{"description":"Every export job for the site","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["exports"],"properties":{"exports":{"type":"array","items":{"$ref":"#/components/schemas/ExportJob"}}}}}}]}}}},
      "ExportJob":{"description":"One export job","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["export"],"properties":{"export":{"$ref":"#/components/schemas/ExportJob"}}}}}]}}}},

      "ShareLinkList":{"description":"Share links on sites the caller may administer","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["links"],"properties":{"links":{"type":"array","items":{"$ref":"#/components/schemas/ShareLink"}}}}}}]}}}},
      "ShareLinkCreated":{"description":"The new link's URL, returned exactly once","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ShareLinkCreated"}}}]}}}},
      "ApiKeyList":{"description":"Key metadata only, never a secret","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["keys"],"properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}}}]}}}},
      "ApiKeyCreated":{"description":"The new credential, returned exactly once","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ApiKeyCreated"}}}]}}}},

      "ReportScheduleList":{"description":"Every schedule on a visible site","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["schedules"],"properties":{"schedules":{"type":"array","items":{"$ref":"#/components/schemas/ReportSchedule"}}}}}}]}}}},
      "ReportSchedule":{"description":"One schedule","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["schedule"],"properties":{"schedule":{"$ref":"#/components/schemas/ReportSchedule"}}}}}]}}}},
      "ReportRetried":{"description":"The schedule whose last delivery was re-queued","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["retried"],"properties":{"retried":{"type":"string"}}}}}]}}}},
      "RecentRanges":{"description":"The user's remembered custom ranges, newest first","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["recent_ranges"],"properties":{"recent_ranges":{"type":"array","items":{"$ref":"#/components/schemas/RecentRange"}}}}}}]}}}},

      "Monitor":{"description":"Monitor configuration, status, and deployment caveats","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Monitor"}}}]}}}},
      "MonitorStatus":{"description":"Monitor configuration and status","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MonitorStatus"}}}]}}}},
      "MonitorProbe":{"description":"One probe result","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MonitorProbe"}}}]}}}},
      "MonitorIncidentList":{"description":"Recent incidents, newest first","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["incidents"],"properties":{"incidents":{"type":"array","items":{"$ref":"#/components/schemas/MonitorIncident"}}}}}}]}}}},
      "MonitorProbeList":{"description":"Recent probes, newest first","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["probes"],"properties":{"probes":{"type":"array","items":{"$ref":"#/components/schemas/MonitorProbe"}}}}}}]}}}},
      "PushDeviceList":{"description":"Enrolled devices plus the public VAPID key and quotas","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["available","public_key","devices","limits"],"properties":{"available":{"type":"boolean"},"public_key":{"type":["string","null"]},"devices":{"type":"array","items":{"$ref":"#/components/schemas/PushDevice"}},"limits":{"type":"object","required":["per_user","per_account"],"properties":{"per_user":{"type":"integer"},"per_account":{"type":"integer"}}}}}}}]}}}},
      "PushDevice":{"description":"The enrolled device","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PushDevice"}}}]}}}},
      "PushRemoved":{"description":"The id of the device that was removed","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["removed"],"properties":{"removed":{"type":"string"}}}}}]}}}},
      "PushQueued":{"description":"The test notification was queued","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["queued"],"properties":{"queued":{"const":true}}}}}]}}}},

      "AlertRuleList":{"description":"Every alert rule on the site","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["alerts"],"properties":{"alerts":{"type":"array","items":{"$ref":"#/components/schemas/AlertRule"}}}}}}]}}}},
      "AlertRule":{"description":"One alert rule","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["alert"],"properties":{"alert":{"$ref":"#/components/schemas/AlertRule"}}}}}]}}}},
      "AlertDeliveryList":{"description":"Recent alert deliveries, newest first","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["deliveries"],"properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/AlertDelivery"}}}}}}]}}}},

      "Retention":{"description":"Effective retention by data class and by site","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Retention"}}}]}}}},
      "RetentionPreview":{"description":"What a candidate retention window would remove now","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["preview"],"properties":{"preview":{"$ref":"#/components/schemas/RetentionPreview"}}}}}]}}}}
    }
  }
}
