Files
monlet/api/openapi.yaml
2026-05-26 16:41:27 +04:00

566 lines
16 KiB
YAML

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
security: []
responses:
"200":
description: Service is alive.
content:
application/json:
schema:
$ref: "#/components/schemas/HealthResponse"
/api/v1/ready:
get:
summary: Readiness check
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
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
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
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
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
parameters:
- $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
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 stored events
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
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 stored on this request.
deduplicated:
type: integer
minimum: 0
description: Number of events whose `event_id` was already known.
HeartbeatRequest:
type: object
required: [agent_id, observed_at, hostname, version]
properties:
agent_id:
type: string
maxLength: 128
pattern: "^[A-Za-z0-9._:-]+$"
observed_at:
type: string
format: date-time
hostname:
type: string
maxLength: 253
version:
type: string
maxLength: 64
mode:
type: string
enum: [prometheus_only, push_only, hybrid]
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.
EventBatchRequest:
type: object
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
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
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
notification_owner:
type: string
enum: [server, prometheus, none]
incident_key:
type: string
maxLength: 256
StoredCheckResultEvent:
description: Stored event returned by `GET /events/query`. 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]
properties:
agent_id:
type: string
hostname:
type: string
status:
type: string
enum: [alive, stale, dead]
last_seen_at:
type: string
format: date-time
version:
type: string
mode:
type: string
enum: [prometheus_only, push_only, hybrid]
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
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