openapi: 3.0.3 info: title: Sentinel API description: | REST API for gathering Linux server and Docker Engine metrics. Sentinel collects system metrics (CPU, memory) and Docker container statistics, storing them in SQLite and providing both current and historical data through REST endpoints. This service is designed for integration with [Coolify.io](https://coolify.io). version: 1.0.0 contact: name: Coolify url: https://coolify.io license: name: Apache-2.0 url: https://www.apache.org/licenses/LICENSE-2.0 servers: - url: http://localhost:8888 description: Local development server - url: https://sentinel.example.com description: Production server security: - bearerAuth: [] tags: - name: Core description: Health check and version endpoints - name: System Metrics description: CPU and memory metrics for the host system - name: Container Metrics description: Metrics for Docker containers - name: Traffic Analytics description: | On-box, aggregate-only web/traffic analytics computed from the reverse-proxy access log. Only present when Sentinel is built with the `traffic` Cargo feature and `TRAFFIC_ENABLED=true` at runtime; see README.md for the full feature description and Coolify handoff requirements. - name: Debug description: Debug-only endpoints (available when DEBUG=true) paths: /api/health: get: summary: Health check description: Check if the service is running tags: - Core security: [] responses: '200': description: Service is healthy content: text/plain: schema: type: string example: ok /api/version: get: summary: Get version description: Get the current version of Sentinel tags: - Core security: [] responses: '200': description: Current version content: text/plain: schema: type: string example: 1.0.0 /api/cpu/current: get: summary: Get current CPU usage description: Retrieve the current CPU usage percentage tags: - System Metrics responses: '200': description: Current CPU usage content: application/json: schema: $ref: '#/components/schemas/CurrentCPUUsage' '401': $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' /api/cpu/history: get: summary: Get CPU usage history description: Retrieve historical CPU usage data with optional date range filtering tags: - System Metrics parameters: - $ref: '#/components/parameters/FromDate' - $ref: '#/components/parameters/ToDate' responses: '200': description: Historical CPU usage data content: application/json: schema: type: array items: $ref: '#/components/schemas/HistoricalCPUUsage' '400': $ref: '#/components/responses/BadRequestError' '401': $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' /api/memory/current: get: summary: Get current memory usage description: Retrieve the current memory usage statistics tags: - System Metrics responses: '200': description: Current memory usage content: application/json: schema: $ref: '#/components/schemas/CurrentMemoryUsage' '401': $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' /api/memory/history: get: summary: Get memory usage history description: Retrieve historical memory usage data with optional date range filtering tags: - System Metrics parameters: - $ref: '#/components/parameters/FromDate' - $ref: '#/components/parameters/ToDate' responses: '200': description: Historical memory usage data content: application/json: schema: type: array items: $ref: '#/components/schemas/HistoricalMemoryUsage' '400': $ref: '#/components/responses/BadRequestError' '401': $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' /api/container/{containerId}/cpu/history: get: summary: Get container CPU usage history description: Retrieve CPU usage history for a specific Docker container tags: - Container Metrics parameters: - name: containerId in: path required: true description: Exact container display name recorded by Sentinel schema: type: string pattern: '^[a-zA-Z0-9._-]+$' example: postgres-db - $ref: '#/components/parameters/FromDate' - $ref: '#/components/parameters/ToDate' responses: '200': description: Container CPU usage history content: application/json: schema: type: array items: $ref: '#/components/schemas/ContainerCPUUsage' '400': $ref: '#/components/responses/BadRequestError' '401': $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/NotFoundError' '500': $ref: '#/components/responses/InternalServerError' /api/container/{containerId}/memory/history: get: summary: Get container memory usage history description: Retrieve memory usage history for a specific Docker container tags: - Container Metrics parameters: - name: containerId in: path required: true description: Exact container display name recorded by Sentinel schema: type: string pattern: '^[a-zA-Z0-9._-]+$' example: postgres-db - $ref: '#/components/parameters/FromDate' - $ref: '#/components/parameters/ToDate' responses: '200': description: Container memory usage history content: application/json: schema: type: array items: $ref: '#/components/schemas/ContainerMemoryUsage' '400': $ref: '#/components/responses/BadRequestError' '401': $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/NotFoundError' '500': $ref: '#/components/responses/InternalServerError' /api/disk/current: get: summary: Get current disk usage description: Latest stored filesystem usage, one entry per real mountpoint tags: - System Metrics responses: '200': description: Current disk usage per mountpoint content: application/json: schema: type: array items: $ref: '#/components/schemas/DiskUsage' '401': $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' /api/disk/history: get: summary: Get disk usage history description: Historical filesystem usage across mountpoints with optional date range filtering tags: - System Metrics parameters: - $ref: '#/components/parameters/FromDate' - $ref: '#/components/parameters/ToDate' responses: '200': description: Historical disk usage data content: application/json: schema: type: array items: $ref: '#/components/schemas/DiskUsage' '400': $ref: '#/components/responses/BadRequestError' '401': $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' /api/container/{containerId}/disk/current: get: summary: Get current container storage description: Latest stored writable-layer and volume size for a specific container (null if none recorded) tags: - Container Metrics parameters: - name: containerId in: path required: true description: Exact container display name recorded by Sentinel schema: type: string pattern: '^[a-zA-Z0-9._-]+$' example: postgres-db responses: '200': description: Current container storage (or null) content: application/json: schema: nullable: true allOf: - $ref: '#/components/schemas/ContainerDiskUsage' '401': $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' /api/container/{containerId}/disk/history: get: summary: Get container storage history description: Historical writable-layer and volume sizes for a specific container tags: - Container Metrics parameters: - name: containerId in: path required: true description: Exact container display name recorded by Sentinel schema: type: string pattern: '^[a-zA-Z0-9._-]+$' example: postgres-db - $ref: '#/components/parameters/FromDate' - $ref: '#/components/parameters/ToDate' responses: '200': description: Container storage history content: application/json: schema: type: array items: $ref: '#/components/schemas/ContainerDiskUsage' '400': $ref: '#/components/responses/BadRequestError' '401': $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' /api/traffic/apps: get: summary: Get recorded apps description: | List every app UUID (or host, for Caddy) that traffic analytics has recorded data for. tags: - Traffic Analytics responses: '200': description: Recorded app identifiers content: application/json: schema: type: array items: type: string example: ["jc4wsgs", "another-app-uuid"] '401': $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/TrafficNotEnabledError' '500': $ref: '#/components/responses/InternalServerError' /api/app/{uuid}/traffic/overview: get: summary: Get app traffic overview description: | Request/bandwidth totals, status-class counts, latency percentiles, and estimated unique visitors for one app, merged across every host it was served on. An app with no data in range returns 200 with every counter zeroed, not 404. tags: - Traffic Analytics parameters: - $ref: '#/components/parameters/AppUuid' - $ref: '#/components/parameters/FromDate' - $ref: '#/components/parameters/ToDate' responses: '200': description: App traffic overview content: application/json: schema: $ref: '#/components/schemas/TrafficOverview' '400': $ref: '#/components/responses/BadRequestError' '401': $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/TrafficNotEnabledError' '500': $ref: '#/components/responses/InternalServerError' /api/app/{uuid}/traffic/paths: get: summary: Get app top paths description: | Busiest request paths for one app, summed across every bucket in range, with per-path latency. A synthetic `__other__` entry absorbs the long tail past the server's top-N cap (TRAFFIC_TOPN). tags: - Traffic Analytics parameters: - $ref: '#/components/parameters/AppUuid' - $ref: '#/components/parameters/FromDate' - $ref: '#/components/parameters/ToDate' - $ref: '#/components/parameters/Limit' responses: '200': description: Top paths for the app content: application/json: schema: type: array items: $ref: '#/components/schemas/TrafficPath' '400': $ref: '#/components/responses/BadRequestError' '401': $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/TrafficNotEnabledError' '500': $ref: '#/components/responses/InternalServerError' /api/app/{uuid}/traffic/breakdown/{dimension}: get: summary: Get app dimension breakdown description: | Top values of one dimension for one app, summed across every bucket in range. A synthetic `__other__` entry absorbs the long tail past the server's top-N cap (TRAFFIC_TOPN). An unrecognized dimension returns an empty array, not an error. tags: - Traffic Analytics parameters: - $ref: '#/components/parameters/AppUuid' - name: dimension in: path required: true description: | One of: status, method, country, referer, browser, os, device, protocol, scheme, tls, cache, bot. schema: type: string example: country - $ref: '#/components/parameters/FromDate' - $ref: '#/components/parameters/ToDate' - $ref: '#/components/parameters/Limit' responses: '200': description: Top values of the dimension for the app content: application/json: schema: type: array items: $ref: '#/components/schemas/TrafficBreakdownEntry' '400': $ref: '#/components/responses/BadRequestError' '401': $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/TrafficNotEnabledError' '500': $ref: '#/components/responses/InternalServerError' /api/traffic/overview: get: summary: Get server-wide traffic overview description: | Request/bandwidth totals, status-class counts, latency percentiles, and estimated unique visitors merged across every app and host on the box. Sketches (t-digest, HyperLogLog) are merged server-side, so the percentiles and visitor estimate are a true cross-app merge, not a sum of per-app estimates. Same shape as the per-app overview. tags: - Traffic Analytics parameters: - $ref: '#/components/parameters/FromDate' - $ref: '#/components/parameters/ToDate' responses: '200': description: Server-wide traffic overview content: application/json: schema: $ref: '#/components/schemas/TrafficOverview' '400': $ref: '#/components/responses/BadRequestError' '401': $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/TrafficNotEnabledError' '500': $ref: '#/components/responses/InternalServerError' /api/traffic/paths: get: summary: Get server-wide top paths description: | Busiest request paths across every app on the box, summed over the range with per-path latency. The same path served by multiple apps is merged into one entry, giving a correct top-N across all apps rather than a merge of per-app top-N lists. tags: - Traffic Analytics parameters: - $ref: '#/components/parameters/FromDate' - $ref: '#/components/parameters/ToDate' - $ref: '#/components/parameters/Limit' responses: '200': description: Server-wide top paths content: application/json: schema: type: array items: $ref: '#/components/schemas/TrafficPath' '400': $ref: '#/components/responses/BadRequestError' '401': $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/TrafficNotEnabledError' '500': $ref: '#/components/responses/InternalServerError' /api/traffic/breakdown/{dimension}: get: summary: Get server-wide dimension breakdown description: | Top values of one dimension across every app on the box, summed over the range. An unrecognized dimension returns an empty array, not an error. tags: - Traffic Analytics parameters: - name: dimension in: path required: true description: | One of: status, method, country, referer, browser, os, device, protocol, scheme, tls, cache, bot. schema: type: string example: country - $ref: '#/components/parameters/FromDate' - $ref: '#/components/parameters/ToDate' - $ref: '#/components/parameters/Limit' responses: '200': description: Top values of the dimension across all apps content: application/json: schema: type: array items: $ref: '#/components/schemas/TrafficBreakdownEntry' '400': $ref: '#/components/responses/BadRequestError' '401': $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/TrafficNotEnabledError' '500': $ref: '#/components/responses/InternalServerError' /api/traffic/attribution: get: summary: Get GeoIP attribution description: | License attribution string for whichever GeoIP data source is currently active (resolved at runtime — see README.md's GeoIP licensing section). tags: - Traffic Analytics responses: '200': description: Active GeoIP source's attribution string, or null content: application/json: schema: $ref: '#/components/schemas/TrafficAttribution' '401': $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/TrafficNotEnabledError' '500': $ref: '#/components/responses/InternalServerError' /api/stats: get: summary: Get database statistics description: | Retrieve database storage statistics and estimated logical table sizes. Only available when DEBUG environment variable is set to true. tags: - Debug responses: '200': description: Database statistics content: application/json: schema: $ref: '#/components/schemas/DatabaseStats' '401': $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/NotFoundError' '500': $ref: '#/components/responses/InternalServerError' components: securitySchemes: bearerAuth: type: http scheme: bearer description: | Bearer token authentication. Set the TOKEN environment variable when running Sentinel and include it in the Authorization header. parameters: FromDate: name: from in: query description: | Start date in ISO 8601 format (UTC timezone). If not provided, defaults to 1970-01-01T00:00:00Z. required: false schema: type: string format: date-time example: 2024-01-15T00:00:00Z ToDate: name: to in: query description: | End date in ISO 8601 format (UTC timezone). If not provided, defaults to current time. required: false schema: type: string format: date-time example: 2024-01-15T23:59:59Z AppUuid: name: uuid in: path required: true description: Coolify app UUID (or host, for Caddy — see README.md) schema: type: string example: jc4wsgs Limit: name: limit in: query description: | Number of ranked entries to return, applied after summing across buckets. Defaults to 50, capped at 1000. required: false schema: type: integer minimum: 1 maximum: 1000 default: 50 example: 20 schemas: CurrentCPUUsage: type: object properties: time: type: string description: Unix timestamp in milliseconds example: "1700000000000" percent: type: number format: float description: CPU usage percentage (0-100) example: 25.5 HistoricalCPUUsage: type: object properties: time: type: string description: Unix timestamp in milliseconds example: "1700000000000" percent: type: string description: CPU usage percentage example: "25.5" human_friendly_time: type: string format: date-time description: ISO 8601 formatted timestamp (only in debug mode) example: "2024-01-15T10:00:00Z" CurrentMemoryUsage: type: object properties: time: type: string description: Unix timestamp in milliseconds example: "1700000000000" total: type: integer format: int64 description: Total memory in bytes example: 16000000000 available: type: integer format: int64 description: Available memory in bytes example: 8000000000 used: type: integer format: int64 description: Used memory in bytes example: 8000000000 usedPercent: type: number format: float description: Memory usage percentage (0-100) example: 50.00 free: type: integer format: int64 description: Free memory in bytes example: 8000000000 HistoricalMemoryUsage: type: object properties: time: type: string description: Unix timestamp in milliseconds example: "1700000000000" total: type: integer format: int64 description: Total memory in bytes example: 16000000000 available: type: integer format: int64 description: Available memory in bytes example: 8000000000 used: type: integer format: int64 description: Used memory in bytes example: 8000000000 usedPercent: type: number format: float description: Memory usage percentage example: 50.00 free: type: integer format: int64 description: Free memory in bytes example: 8000000000 human_friendly_time: type: string format: date-time description: ISO 8601 formatted timestamp (only in debug mode) example: "2024-01-15T10:00:00Z" ContainerCPUUsage: type: object properties: time: type: string description: Unix timestamp in milliseconds example: "1700000000000" percent: type: string description: CPU usage percentage for the container example: "12.5" human_friendly_time: type: string format: date-time description: ISO 8601 formatted timestamp (only in debug mode) example: "2024-01-15T10:00:00Z" ContainerMemoryUsage: type: object properties: time: type: string description: Unix timestamp in milliseconds example: "1700000000000" total: type: integer format: int64 description: Total container memory limit in bytes example: 4000000000 available: type: integer format: int64 description: Available memory in bytes example: 2000000000 used: type: integer format: int64 description: Used memory in bytes example: 2000000000 usedPercent: type: number format: float description: Memory usage percentage example: 50.00 free: type: integer format: int64 description: Free memory in bytes example: 2000000000 human_friendly_time: type: string format: date-time description: ISO 8601 formatted timestamp (only in debug mode) example: "2024-01-15T10:00:00Z" DiskUsage: type: object properties: time: type: string description: Unix timestamp in milliseconds example: "1700000000000" mount: type: string description: Filesystem mountpoint example: "/" total: type: integer format: int64 description: Total capacity in bytes example: 500000000000 used: type: integer format: int64 description: Used space in bytes example: 250000000000 available: type: integer format: int64 description: Available space in bytes example: 250000000000 usedPercent: type: number format: float description: Disk usage percentage (0-100) example: 50.00 human_friendly_time: type: string format: date-time description: ISO 8601 formatted timestamp (only in debug mode) example: "2024-01-15T10:00:00Z" ContainerDiskUsage: type: object properties: time: type: string description: Unix timestamp in milliseconds example: "1700000000000" writableLayer: type: integer format: int64 description: Docker writable-layer size in bytes (SizeRw) example: 12000000 volumesTotal: type: integer format: int64 description: Summed size in bytes of the container's volume/bind mounts example: 340000000 human_friendly_time: type: string format: date-time description: ISO 8601 formatted timestamp (only in debug mode) example: "2024-01-15T10:00:00Z" TrafficStatusBreakdown: type: object properties: s2xx: type: integer format: int64 example: 124000 s3xx: type: integer format: int64 example: 3200 s4xx: type: integer format: int64 example: 1100 s5xx: type: integer format: int64 example: 40 TrafficLatency: type: object description: | Approximate latency quantiles in milliseconds (t-digest estimate). 0.0 on every field when the range holds no decodable latency sketch. properties: p50: type: number format: float example: 42.0 p95: type: number format: float example: 210.5 p99: type: number format: float example: 480.0 TrafficOverview: type: object properties: requests: type: integer format: int64 example: 128340 bytes_in: type: integer format: int64 example: 15200000 bytes_out: type: integer format: int64 example: 981000000 status: $ref: '#/components/schemas/TrafficStatusBreakdown' latency: $ref: '#/components/schemas/TrafficLatency' unique_visitors: type: integer format: int64 description: Approximate distinct client IPs (HyperLogLog++ estimate, ~1-2% error) example: 8421 TrafficPath: type: object description: | One row of the top-paths table, summed over every bucket in the range. p99 is deliberately omitted to keep large responses small. properties: path: type: string example: /api/checkout requests: type: integer format: int64 example: 5210 bytes_out: type: integer format: int64 example: 41200000 p50: type: number format: float example: 38.0 p95: type: number format: float example: 190.0 TrafficBreakdownEntry: type: object description: One value of a breakdown dimension, summed over every bucket in the range. properties: value: type: string example: US requests: type: integer format: int64 example: 42000 bytes_out: type: integer format: int64 example: 320000000 TrafficAttribution: type: object properties: attribution: type: string nullable: true description: | The active GeoIP source's required attribution string, or null when GeoIP is disabled, not yet resolved, or the active source is an unrecognized GEOIP_DB_URL override. example: "This product includes GeoLite2 data created by MaxMind, available from https://www.maxmind.com" DatabaseStats: type: object properties: row_count: type: integer description: Total number of rows in the database example: 10000 storage_usage_kb: type: string description: Storage usage in kilobytes example: "1024.50" storage_usage_mb: type: string description: Storage usage in megabytes example: "1.00" memory_usage: $ref: '#/components/schemas/CurrentMemoryUsage' table_sizes: type: array items: type: object properties: table_name: type: string description: Name of the database table example: cpu_usage row_count: type: integer description: Number of rows in the table example: 600 size_mb: type: string description: Estimated logical table size in megabytes example: "0.50" size_kb: type: string description: Estimated logical table size in kilobytes example: "512.00" Error: type: object properties: error: type: string description: Error message example: Invalid date format responses: BadRequestError: description: Bad request - invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Invalid date format for 'from' parameter UnauthorizedError: description: Unauthorized - missing or invalid authentication token content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Unauthorized NotFoundError: description: Not found - resource does not exist content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Container not found TrafficNotEnabledError: description: | Traffic analytics isn't enabled — the binary lacks the `traffic` Cargo feature, TRAFFIC_ENABLED isn't true, or the analytics database failed to open. content: application/json: schema: $ref: '#/components/schemas/Error' example: error: traffic analytics not enabled InternalServerError: description: Internal server error content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Internal server error