openapi: 3.1.0 info: title: Monlet API version: 1.0.0 description: | Monlet v1 API contract. Stage 1 frozen. All endpoints under `/api/v1`. Authentication uses a single shared Bearer token (see ADR-0007). Agent-facing ingestion endpoints (`/heartbeat`, `/events`) and read-only UI-facing endpoints share the same security scheme in v1. servers: - url: http://127.0.0.1:8000 security: - bearerAuth: [] paths: /api/v1/health: get: summary: Health check operationId: getHealth security: [] responses: "200": description: Service is alive. content: application/json: schema: $ref: "#/components/schemas/HealthResponse" /api/v1/ready: get: summary: Readiness check operationId: getReady security: [] responses: "200": description: Service is ready. content: application/json: schema: $ref: "#/components/schemas/HealthResponse" "503": description: Service is not ready. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /api/v1/heartbeat: post: summary: Ingest agent heartbeat operationId: ingestHeartbeat requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/HeartbeatRequest" responses: "202": description: Heartbeat accepted. content: application/json: schema: $ref: "#/components/schemas/AcceptedResponse" "400": $ref: "#/components/responses/ValidationError" "401": $ref: "#/components/responses/Unauthorized" "413": $ref: "#/components/responses/PayloadTooLarge" "500": $ref: "#/components/responses/InternalError" /api/v1/events: post: summary: Ingest agent check result events operationId: ingestEvents requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/EventBatchRequest" responses: "202": description: Events accepted. content: application/json: schema: $ref: "#/components/schemas/EventBatchAcceptedResponse" "400": $ref: "#/components/responses/ValidationError" "401": $ref: "#/components/responses/Unauthorized" "413": $ref: "#/components/responses/PayloadTooLarge" "500": $ref: "#/components/responses/InternalError" /api/v1/agents: get: summary: List agents operationId: listAgents parameters: - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" responses: "200": description: Agent list. content: application/json: schema: allOf: - $ref: "#/components/schemas/PageEnvelope" - type: object required: [items] properties: items: type: array items: $ref: "#/components/schemas/Agent" "401": $ref: "#/components/responses/Unauthorized" /api/v1/agents/{agent_id}: get: summary: Get agent detail operationId: getAgent parameters: - $ref: "#/components/parameters/AgentId" responses: "200": description: Agent detail. content: application/json: schema: $ref: "#/components/schemas/Agent" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" /api/v1/checks: get: summary: List current check states operationId: listChecks parameters: - name: agent_id in: query required: false schema: type: string maxLength: 128 pattern: "^[A-Za-z0-9._:-]+$" - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" responses: "200": description: Check state list. content: application/json: schema: allOf: - $ref: "#/components/schemas/PageEnvelope" - type: object required: [items] properties: items: type: array items: $ref: "#/components/schemas/CheckState" "401": $ref: "#/components/responses/Unauthorized" /api/v1/incidents: get: summary: List incidents operationId: listIncidents parameters: - name: state in: query required: false schema: type: string enum: [open, resolved] - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" responses: "200": description: Incident list. content: application/json: schema: allOf: - $ref: "#/components/schemas/PageEnvelope" - type: object required: [items] properties: items: type: array items: $ref: "#/components/schemas/Incident" "401": $ref: "#/components/responses/Unauthorized" /api/v1/events/query: get: summary: Query check status-change history operationId: queryEvents parameters: - name: agent_id in: query required: false schema: type: string maxLength: 128 pattern: "^[A-Za-z0-9._:-]+$" - name: check_id in: query required: false schema: type: string maxLength: 128 pattern: "^[A-Za-z0-9._:-]+$" - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" responses: "200": description: Event list. content: application/json: schema: allOf: - $ref: "#/components/schemas/PageEnvelope" - type: object required: [items] properties: items: type: array items: $ref: "#/components/schemas/StoredCheckResultEvent" "401": $ref: "#/components/responses/Unauthorized" /api/v1/notifiers/outbox: get: summary: List notification outbox items operationId: listOutbox parameters: - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limit" responses: "200": description: Outbox list. content: application/json: schema: allOf: - $ref: "#/components/schemas/PageEnvelope" - type: object required: [items] properties: items: type: array items: $ref: "#/components/schemas/NotificationOutboxItem" "401": $ref: "#/components/responses/Unauthorized" components: securitySchemes: bearerAuth: type: http scheme: bearer description: Shared static Bearer token in v1. See ADR-0007. parameters: AgentId: name: agent_id in: path required: true schema: type: string maxLength: 128 pattern: "^[A-Za-z0-9._:-]+$" Cursor: name: cursor in: query required: false description: Opaque pagination cursor returned in `next_cursor`. schema: type: string maxLength: 512 Limit: name: limit in: query required: false schema: type: integer minimum: 1 maximum: 500 default: 100 responses: Unauthorized: description: Authentication failed. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" NotFound: description: Resource not found. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" PayloadTooLarge: description: Payload exceeds configured limit (1 MiB). content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" ValidationError: description: Request body or parameters failed validation. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" InternalError: description: Unexpected server error. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" schemas: HealthResponse: type: object required: [status] properties: status: type: string enum: [ok] ErrorResponse: type: object required: [error] properties: error: type: object required: [code, message, request_id] properties: code: type: string enum: - unauthorized - not_found - validation - payload_too_large - rate_limited - internal - not_ready message: type: string request_id: type: string description: Server-generated request id, mirrored from X-Request-Id. PageEnvelope: type: object properties: next_cursor: type: [string, "null"] description: Pass back as `cursor` to fetch the next page. Null/absent if no more results. AcceptedResponse: type: object required: [accepted] properties: accepted: type: boolean EventBatchAcceptedResponse: type: object required: [accepted, deduplicated] properties: accepted: type: integer minimum: 0 description: | Number of events that produced a new status-change row on this request. deduplicated: type: integer minimum: 0 description: | Number of events that were not stored as a change. Includes events whose `event_id` was already known, events whose `(status, exit_code)` matched the previously observed value for the same check, and events received late (older than the current `last_observed_at` of the check). HeartbeatRequest: type: object additionalProperties: false required: [agent_id, observed_at, hostname, features] properties: agent_id: type: string maxLength: 128 pattern: "^[A-Za-z0-9._:-]+$" observed_at: type: string format: date-time description: UTC RFC3339 timestamp. Timezone is required; use `Z` or `+00:00`. Non-UTC offsets are rejected. hostname: type: string maxLength: 253 features: $ref: "#/components/schemas/AgentFeatures" labels: type: object maxProperties: 32 propertyNames: maxLength: 64 pattern: "^[A-Za-z0-9._:-]+$" additionalProperties: type: string maxLength: 256 description: Up to 32 labels. Key max 64, value max 256. Monlet agents reserve `monlet_agent_version`. Secret-like keys are rejected. AgentFeatures: type: object additionalProperties: false required: [push, metrics] properties: push: type: boolean description: Agent pushes heartbeats/events to the server. metrics: type: boolean description: Agent exposes a Prometheus-compatible metrics endpoint. EventBatchRequest: type: object additionalProperties: false required: [agent_id, events] properties: agent_id: type: string maxLength: 128 pattern: "^[A-Za-z0-9._:-]+$" events: type: array minItems: 1 maxItems: 200 items: $ref: "#/components/schemas/CheckResultEvent" CheckResultEvent: type: object additionalProperties: false description: | Event body used inside `EventBatchRequest.events`. The owning `agent_id` is supplied once at the batch envelope; per-event `agent_id` is intentionally absent so requests cannot mix or spoof other agents under a shared token. Server response endpoints (`GET /events/query`) include `agent_id` separately in their item schema. required: - event_id - check_id - observed_at - status - exit_code - duration_ms properties: event_id: type: string description: UUIDv7 string (lower-case, with dashes). See ADR-0006. minLength: 36 maxLength: 36 pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" check_id: type: string maxLength: 128 pattern: "^[A-Za-z0-9._:-]+$" observed_at: type: string format: date-time description: UTC RFC3339 timestamp. Timezone is required; use `Z` or `+00:00`. Non-UTC offsets are rejected. status: type: string enum: [ok, warning, critical, unknown] exit_code: type: integer duration_ms: type: integer minimum: 0 output: type: string maxLength: 8192 description: | Limit is 8 KiB measured in UTF-8 **bytes** (not characters). Agent truncates on a UTF-8 boundary before send and appends marker `...[truncated N bytes]`. `maxLength: 8192` here is a coarse schema-level upper bound; servers MUST additionally reject events whose UTF-8 byte length exceeds 8192 with `400 validation`. output_truncated: type: boolean default: false notifications_enabled: type: boolean default: true description: If false, the server stores state/incidents but does not enqueue notifications for this event. incident_key: type: string maxLength: 256 StoredCheckResultEvent: description: | Stored status-change event returned by `GET /events/query`. The server persists only events that change `(status, exit_code)` versus the previous observed value for the same `(agent_id, check_id)`. Repeated identical results are not stored. Includes the resolved `agent_id` and server-side `received_at`. allOf: - $ref: "#/components/schemas/CheckResultEvent" - type: object required: [agent_id, received_at] properties: agent_id: type: string maxLength: 128 pattern: "^[A-Za-z0-9._:-]+$" received_at: type: string format: date-time Agent: type: object required: [agent_id, hostname, status, last_seen_at, features] properties: agent_id: type: string hostname: type: string status: type: string enum: [alive, stale, dead] last_seen_at: type: string format: date-time features: $ref: "#/components/schemas/AgentFeatures" labels: type: object additionalProperties: type: string CheckState: type: object required: [agent_id, check_id, status, last_observed_at] properties: agent_id: type: string check_id: type: string status: type: string enum: [ok, warning, critical, unknown] last_observed_at: type: string format: date-time exit_code: type: integer incident_key: type: string summary: type: string maxLength: 8192 Incident: type: object required: [id, incident_key, state, severity, opened_at] properties: id: type: string incident_key: type: string state: type: string enum: [open, resolved] severity: type: string enum: [warning, critical, unknown] agent_id: type: string check_id: type: string opened_at: type: string format: date-time resolved_at: type: [string, "null"] format: date-time summary: type: string NotificationOutboxItem: type: object required: [id, notifier, state, incident_id, event_type, attempts] properties: id: type: string notifier: type: string state: type: string enum: [pending, sending, sent, retry, failed, discarded] incident_id: type: string event_type: type: string enum: [firing, resolved] attempts: type: integer minimum: 0 next_attempt_at: type: [string, "null"] format: date-time last_error: type: [string, "null"] updated_at: type: string format: date-time