566 lines
16 KiB
YAML
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
|