# Analytics API

3 operations under /api/v1. Errors are RFC 9457 problem+json with a stable `code` field. The complete schemas are in https://nebulactrl.com/api/v1/openapi.json.

## GET /analytics/traffic

Traffic analytics across servers

Workspace-wide report, optionally filtered by server, service or environment. Without a service or environment filter it needs `servers:read`; with one it needs `projects:read`. Project-restricted tokens only see their own services. Window to report. Minute data covers 24 hours; longer ranges read hourly (default 7 days kept) or daily (90 days) rows.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `range` (query, enum: 5m | 10m | 15m | 30m | 1h | 6h | 12h | 24h | 7d | 30d | 90d): Window to report
- `compare` (query, enum:  | prev): prev adds the previous equal window as comparison
- `host` (query, string): Restrict to one hostname
- `pages` (query, boolean): true (default) ranks top paths by page views (documents); false ranks every request
- `serverId` (query, string (uuid)): Restrict to one server
- `serviceId` (query, string (uuid)): Restrict to one service (needs projects:read)
- `environmentId` (query, string (uuid)): Restrict to the services of one environment (needs projects:read)

Response 200: OK (Report)
  - `activeHours` (integer (int64)[] | null[] | null)
  - `comparison` (Summary)
  - `deployments` (DeploymentMarker[] | null, required)
  - `geo` (Geo)
  - `geoReason` (enum: geo_disabled | traffic_disabled): Why geo is absent
  - `notFound` (TopItem[] | null, required): Most requested paths answered 404
  - `range` (RangeInfo, required)
  - `series` (SeriesPoint[] | null, required)
  - `sparklines` (Sparklines, required)
  - `statusCodes` (StatusCount[] | null, required)
  - `summary` (Summary, required)
  - `topBrowsers` (TopItem[] | null, required)
  - `topDevices` (TopItem[] | null, required)
  - `topHosts` (TopItem[] | null, required)
  - `topOS` (TopItem[] | null, required)
  - `topPaths` (TopItem[] | null, required)
  - `topPathsKind` (enum: pages | requests, required)
  - `topReferrers` (TopItem[] | null, required)
  - `unrouted` (Unrouted)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## GET /servers/{serverId}/analytics

Traffic analytics of every host on a server

Same payload as the service report plus `unrouted` (requests that matched no service). Window to report. Minute data covers 24 hours; longer ranges read hourly (default 7 days kept) or daily (90 days) rows.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `serverId` (path, string, required)
- `range` (query, enum: 5m | 10m | 15m | 30m | 1h | 6h | 12h | 24h | 7d | 30d | 90d): Window to report
- `compare` (query, enum:  | prev): prev adds the previous equal window as comparison
- `host` (query, string): Restrict to one hostname
- `pages` (query, boolean): true (default) ranks top paths by page views (documents); false ranks every request

Response 200: OK (Report)
  - `activeHours` (integer (int64)[] | null[] | null)
  - `comparison` (Summary)
  - `deployments` (DeploymentMarker[] | null, required)
  - `geo` (Geo)
  - `geoReason` (enum: geo_disabled | traffic_disabled): Why geo is absent
  - `notFound` (TopItem[] | null, required): Most requested paths answered 404
  - `range` (RangeInfo, required)
  - `series` (SeriesPoint[] | null, required)
  - `sparklines` (Sparklines, required)
  - `statusCodes` (StatusCount[] | null, required)
  - `summary` (Summary, required)
  - `topBrowsers` (TopItem[] | null, required)
  - `topDevices` (TopItem[] | null, required)
  - `topHosts` (TopItem[] | null, required)
  - `topOS` (TopItem[] | null, required)
  - `topPaths` (TopItem[] | null, required)
  - `topPathsKind` (enum: pages | requests, required)
  - `topReferrers` (TopItem[] | null, required)
  - `unrouted` (Unrouted)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## GET /services/{serviceId}/analytics

Traffic analytics of a service

Aggregated from Traefik access logs on the service's server: requests, bandwidth, unique visitors (per UTC day, bots excluded), latency percentiles from merged histograms, status codes and top lists. Raw access logs, IPs and user agents are never stored. Window to report. Minute data covers 24 hours; longer ranges read hourly (default 7 days kept) or daily (90 days) rows.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `serviceId` (path, string, required)
- `range` (query, enum: 5m | 10m | 15m | 30m | 1h | 6h | 12h | 24h | 7d | 30d | 90d): Window to report
- `compare` (query, enum:  | prev): prev adds the previous equal window as comparison
- `host` (query, string): Restrict to one hostname of the service
- `pages` (query, boolean): true (default) ranks top paths by page views (documents); false ranks every request

Response 200: OK (Report)
  - `activeHours` (integer (int64)[] | null[] | null)
  - `comparison` (Summary)
  - `deployments` (DeploymentMarker[] | null, required)
  - `geo` (Geo)
  - `geoReason` (enum: geo_disabled | traffic_disabled): Why geo is absent
  - `notFound` (TopItem[] | null, required): Most requested paths answered 404
  - `range` (RangeInfo, required)
  - `series` (SeriesPoint[] | null, required)
  - `sparklines` (Sparklines, required)
  - `statusCodes` (StatusCount[] | null, required)
  - `summary` (Summary, required)
  - `topBrowsers` (TopItem[] | null, required)
  - `topDevices` (TopItem[] | null, required)
  - `topHosts` (TopItem[] | null, required)
  - `topOS` (TopItem[] | null, required)
  - `topPaths` (TopItem[] | null, required)
  - `topPathsKind` (enum: pages | requests, required)
  - `topReferrers` (TopItem[] | null, required)
  - `unrouted` (Unrouted)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error
