openapi: 3.0.3 info: title: Oikos API version: 1.0.0 contact: name: dtoro license: name: Private url: https://example.invalid/private description: 'Control-plane API for the Oikos homelab OS. This file is the **source of truth** (contract-first): Go server stubs are generated with oapi-codegen, clients (homelab CLI, future UIs) from the same spec. Conventions (plan R3-3): - Errors are RFC 9457 `application/problem+json`. - Lists use `{items, next_cursor}` with cursor pagination (default limit 50, max 200). - Unsafe POSTs accept `Idempotency-Key` (24h replay window). - Mutable resources carry `version`; GET returns `ETag`, PATCH requires `If-Match` (412 on mismatch). - Timestamps are RFC 3339 UTC. - Entities are addressable by UUID or slug (`host:hubris`). The MCP interface (streamable HTTP, official Go SDK) is mounted at `/mcp` on the same binary and is out of scope for this document; its tools wrap the same service layer as these endpoints. ' servers: - url: /api/v1 tags: - name: entities description: Inventory graph — entities and relationships - name: clients description: Client enrollment, context distribution, secrets - name: ontology description: Entity types, relationship types, lifecycles - name: signals description: Signals and checks (observe) - name: executions description: Executions, classifications, approvals (decide/act) - name: learning description: Patterns and skills (learn) - name: policy description: Risk classes, approval rules, autonomy - name: knowledge description: Knowledge graph search - name: observability description: Metrics, trends, audit, events, health - name: system description: Export, health security: - bearerAuth: [] paths: /entities: get: tags: - entities operationId: listEntities summary: List entities x-required-scope: viewer parameters: - name: type in: query schema: type: string description: Filter by entity type (includes descendants) - name: state in: query schema: type: string - name: domain in: query schema: type: string - name: layer in: query schema: type: string - name: q in: query schema: type: string description: Substring match on slug/name - $ref: '#/components/parameters/Cursor' - $ref: '#/components/parameters/Limit' responses: '200': description: Entity list content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Entity' next_cursor: type: string nullable: true default: $ref: '#/components/responses/Problem' post: tags: - entities operationId: createEntity summary: Create an entity x-required-scope: operator parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EntityCreate' responses: '201': description: Created headers: ETag: $ref: '#/components/headers/ETag' content: application/json: schema: $ref: '#/components/schemas/Entity' default: $ref: '#/components/responses/Problem' /entities/{id}: parameters: - $ref: '#/components/parameters/EntityId' get: tags: - entities operationId: getEntity summary: Get entity by UUID or slug x-required-scope: viewer responses: '200': description: Entity detail headers: ETag: $ref: '#/components/headers/ETag' content: application/json: schema: $ref: '#/components/schemas/Entity' default: $ref: '#/components/responses/Problem' patch: tags: - entities operationId: patchEntity summary: Update attributes or transition lifecycle state description: 'Lifecycle transitions are validated against the type''s lifecycle_def; illegal transitions return 409 (invalid-transition). Policy-gated actions may return 403 (approval-required). ' x-required-scope: operator parameters: - $ref: '#/components/parameters/IfMatch' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EntityPatch' responses: '200': description: Updated entity headers: ETag: $ref: '#/components/headers/ETag' content: application/json: schema: $ref: '#/components/schemas/Entity' default: $ref: '#/components/responses/Problem' /entities/{id}/relations: parameters: - $ref: '#/components/parameters/EntityId' get: tags: - entities operationId: getEntityRelations summary: Direct relationships of an entity (both directions) x-required-scope: viewer parameters: - name: rel_type in: query schema: type: string - name: direction in: query schema: type: string enum: - out - in - both default: both responses: '200': description: Relationships content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Relationship' default: $ref: '#/components/responses/Problem' /relationships: post: tags: - entities operationId: createRelationship summary: Create a relationship edge description: Endpoint types validated against relationship_types (hierarchy-aware); cardinality enforced. x-required-scope: operator parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RelationshipCreate' responses: '201': description: Created content: application/json: schema: $ref: '#/components/schemas/Relationship' default: $ref: '#/components/responses/Problem' delete: tags: - entities operationId: endRelationship summary: End a relationship (sets valid_to; the edge is kept for history) x-required-scope: operator parameters: - name: source in: query required: true schema: type: string - name: target in: query required: true schema: type: string - name: rel_type in: query required: true schema: type: string responses: '204': description: Relationship ended default: $ref: '#/components/responses/Problem' /graph: get: tags: - entities operationId: getGraph summary: Subgraph for visualization (nodes + edges) x-required-scope: viewer parameters: - name: root in: query schema: type: string description: Start entity (UUID or slug); omit for whole graph (capped) - name: depth in: query schema: type: integer default: 2 maximum: 5 - name: rel_type in: query schema: type: array items: type: string style: form explode: true - name: include in: query schema: type: array items: type: string enum: - status style: form explode: true description: include=status joins entity_status and populates GraphView.health responses: '200': description: Graph view content: application/json: schema: $ref: '#/components/schemas/GraphView' default: $ref: '#/components/responses/Problem' /entities/{id}/blast-radius: parameters: - $ref: '#/components/parameters/EntityId' get: tags: - entities operationId: getBlastRadius summary: Entities affected if this entity fails x-required-scope: viewer parameters: - name: depth in: query schema: type: integer default: 3 maximum: 5 responses: '200': description: Affected entities with graph distance content: application/json: schema: type: object required: - items properties: items: type: array items: type: object required: - entity - depth properties: entity: $ref: '#/components/schemas/Entity' depth: type: integer default: $ref: '#/components/responses/Problem' /clients/enroll: post: tags: - clients operationId: enrollClient summary: Enroll a new client — issue age key, create Infisical identity description: > Validates mesh IP, generates an age keypair, creates an Infisical machine identity, and transitions the entity to provisioning. Caller must already have an entity in planned or provisioning state. x-required-scope: agent requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EnrollRequest' responses: '200': description: Enrollment response with keys and identity content: application/json: schema: $ref: '#/components/schemas/EnrollResponse' default: $ref: '#/components/responses/Problem' /clients/{slug}/context: parameters: - $ref: '#/components/parameters/EntitySlug' get: tags: - clients operationId: getClientContext summary: Get agent context delta since a timestamp description: > Returns which agent files, tools, and SOPS config changed since the given timestamp. Thin clients poll this instead of git pull. x-required-scope: agent parameters: - name: since in: query schema: type: string format: date-time description: Return only changes since this timestamp (RFC 3339) responses: '200': description: Context delta content: application/json: schema: $ref: '#/components/schemas/ClientContext' default: $ref: '#/components/responses/Problem' /clients/{slug}/secrets: parameters: - $ref: '#/components/parameters/EntitySlug' get: tags: - clients operationId: getClientSecrets summary: List secrets accessible to this client description: Infisical-secured secrets scoped to the client's machine identity x-required-scope: agent responses: '200': description: Secret keys accessible to this client content: application/json: schema: $ref: '#/components/schemas/ClientSecrets' default: $ref: '#/components/responses/Problem' /entities/provision: post: tags: - entities operationId: provisionEntity summary: Provision a compute entity (LXC, VM, container) on a host description: > Creates the entity in planned state, validates constraints (VMID, IP, capacity, template), classifies the action against policy, and transitions to provisioning on operator approval. x-required-scope: operator parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ProvisionRequest' responses: '201': description: Entity created, provisioning queued headers: ETag: $ref: '#/components/headers/ETag' content: application/json: schema: $ref: '#/components/schemas/ProvisionResponse' default: $ref: '#/components/responses/Problem' /entities/{slug}/provision/status: parameters: - $ref: '#/components/parameters/EntitySlug' get: tags: - entities operationId: getProvisionStatus summary: Poll provisioning progress for a compute entity x-required-scope: viewer responses: '200': description: Provisioning steps with status content: application/json: schema: $ref: '#/components/schemas/ProvisionStatus' default: $ref: '#/components/responses/Problem' /ontology: get: tags: - ontology operationId: getOntology summary: Full ontology — entity types, relationship types, lifecycles x-required-scope: viewer responses: '200': description: Ontology content: application/json: schema: type: object required: - entity_types - relationship_types - lifecycles properties: entity_types: type: array items: $ref: '#/components/schemas/EntityType' relationship_types: type: array items: $ref: '#/components/schemas/RelationshipType' lifecycles: type: array items: $ref: '#/components/schemas/LifecycleDef' default: $ref: '#/components/responses/Problem' /ontology/entity-types: post: tags: - ontology operationId: createEntityType summary: Extend the ontology with a new entity type description: Policy-gated as config_mutation (creates a meta-approval when required). x-required-scope: operator parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EntityTypeCreate' responses: '201': description: Created content: application/json: schema: $ref: '#/components/schemas/EntityType' '202': $ref: '#/components/responses/PendingApproval' default: $ref: '#/components/responses/Problem' /ontology/entity-types/{name}: parameters: - name: name in: path required: true schema: type: string patch: tags: - ontology operationId: patchEntityType summary: Update or deprecate an entity type description: Hard delete is not supported while instances exist — deprecate instead (plan D3). x-required-scope: operator parameters: - $ref: '#/components/parameters/IfMatch' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EntityTypePatch' responses: '200': description: Updated content: application/json: schema: $ref: '#/components/schemas/EntityType' '202': $ref: '#/components/responses/PendingApproval' default: $ref: '#/components/responses/Problem' /signals: get: tags: - signals operationId: listSignals summary: List signals x-required-scope: viewer parameters: - name: state in: query schema: type: string - name: severity in: query schema: type: string enum: - info - warning - critical - name: entity_id in: query schema: type: string - name: kind in: query schema: type: string - $ref: '#/components/parameters/Cursor' - $ref: '#/components/parameters/Limit' responses: '200': description: Signals content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Signal' next_cursor: type: string nullable: true default: $ref: '#/components/responses/Problem' /signals/{id}/ack: parameters: - $ref: '#/components/parameters/EntityId' post: tags: - signals operationId: ackSignal summary: Acknowledge a signal x-required-scope: operator responses: '200': $ref: '#/components/responses/SignalUpdated' default: $ref: '#/components/responses/Problem' /signals/{id}/resolve: parameters: - $ref: '#/components/parameters/EntityId' post: tags: - signals operationId: resolveSignal summary: Resolve a signal manually x-required-scope: operator requestBody: content: application/json: schema: type: object properties: note: type: string responses: '200': $ref: '#/components/responses/SignalUpdated' default: $ref: '#/components/responses/Problem' /signals/{id}/mute: parameters: - $ref: '#/components/parameters/EntityId' post: tags: - signals operationId: muteSignal summary: Mute a signal for a TTL x-required-scope: operator requestBody: required: true content: application/json: schema: type: object required: - mute_until properties: mute_until: type: string format: date-time note: type: string responses: '200': $ref: '#/components/responses/SignalUpdated' default: $ref: '#/components/responses/Problem' /checks: get: tags: - signals operationId: listChecks summary: List check definitions x-required-scope: viewer parameters: - name: kind in: query schema: type: string - name: target in: query schema: type: string - name: enabled in: query schema: type: boolean - $ref: '#/components/parameters/Cursor' - $ref: '#/components/parameters/Limit' responses: '200': description: Checks content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Check' next_cursor: type: string nullable: true default: $ref: '#/components/responses/Problem' post: tags: - signals operationId: createCheck summary: Create a check (checks-as-data, plan R3-7) x-required-scope: operator parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CheckCreate' responses: '201': description: Created content: application/json: schema: $ref: '#/components/schemas/Check' default: $ref: '#/components/responses/Problem' /checks/{id}: parameters: - $ref: '#/components/parameters/EntityId' patch: tags: - signals operationId: patchCheck summary: Update or disable a check x-required-scope: operator parameters: - $ref: '#/components/parameters/IfMatch' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CheckPatch' responses: '200': description: Updated content: application/json: schema: $ref: '#/components/schemas/Check' default: $ref: '#/components/responses/Problem' /executions: get: tags: - executions operationId: listExecutions summary: List executions x-required-scope: viewer parameters: - name: status in: query schema: type: string - name: target in: query schema: type: string - name: action in: query schema: type: string - name: correlation_id in: query schema: type: string - $ref: '#/components/parameters/Cursor' - $ref: '#/components/parameters/Limit' responses: '200': description: Executions content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Execution' next_cursor: type: string nullable: true default: $ref: '#/components/responses/Problem' post: tags: - executions operationId: requestExecution summary: Request an execution (classify → approval check → enqueue) description: 'The handler classifies the (entity, action), checks policy/autonomy, and either enqueues the execution (auto-approved) or creates an approval request and returns the execution in `proposed` state. Also exposed to the agent role — this is the ONLY way agents act. ' x-required-scope: agent parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ExecutionRequest' responses: '201': description: Execution created (may be pending approval) content: application/json: schema: $ref: '#/components/schemas/Execution' default: $ref: '#/components/responses/Problem' /executions/{id}: parameters: - $ref: '#/components/parameters/EntityId' get: tags: - executions operationId: getExecution summary: Execution status + result x-required-scope: viewer responses: '200': description: Execution content: application/json: schema: $ref: '#/components/schemas/Execution' default: $ref: '#/components/responses/Problem' /executions/{id}/cancel: parameters: - $ref: '#/components/parameters/EntityId' post: tags: - executions operationId: cancelExecution summary: Cancel a proposed/executing execution x-required-scope: operator responses: '200': description: Cancelled content: application/json: schema: $ref: '#/components/schemas/Execution' default: $ref: '#/components/responses/Problem' /classifications: get: tags: - executions operationId: listClassifications summary: Classifier decisions (the autonomous-decision audit trail) x-required-scope: viewer parameters: - name: signal_id in: query schema: type: string - name: entity_id in: query schema: type: string - name: route in: query schema: type: string enum: - auto-act - escalate - hold - $ref: '#/components/parameters/Cursor' - $ref: '#/components/parameters/Limit' responses: '200': description: Classifications content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Classification' next_cursor: type: string nullable: true default: $ref: '#/components/responses/Problem' /approvals: get: tags: - executions operationId: listApprovals summary: List approvals x-required-scope: viewer parameters: - name: status in: query schema: type: string enum: - pending - approved - denied - expired - revoked - name: kind in: query schema: type: string enum: - execution - policy-change - pattern-activation - $ref: '#/components/parameters/Cursor' - $ref: '#/components/parameters/Limit' responses: '200': description: Approvals content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Approval' next_cursor: type: string nullable: true default: $ref: '#/components/responses/Problem' /approvals/{id}/decision: parameters: - $ref: '#/components/parameters/EntityId' post: tags: - executions operationId: decideApproval summary: Approve or deny (single-use token verified server-side) x-required-scope: operator parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: type: object required: - decision properties: decision: type: string enum: - approve - deny - revoke note: type: string token: type: string description: HMAC approval token (single-use, verified server-side) responses: '200': description: Decision recorded content: application/json: schema: $ref: '#/components/schemas/Approval' default: $ref: '#/components/responses/Problem' /patterns: get: tags: - learning operationId: listPatterns summary: List patterns x-required-scope: viewer parameters: - name: entity_type in: query schema: type: string - name: action in: query schema: type: string - name: status in: query schema: type: string - $ref: '#/components/parameters/Cursor' - $ref: '#/components/parameters/Limit' responses: '200': description: Patterns content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Pattern' next_cursor: type: string nullable: true default: $ref: '#/components/responses/Problem' /patterns/{id}: parameters: - $ref: '#/components/parameters/EntityId' patch: tags: - learning operationId: patchPattern summary: Transition a pattern (activate / invalidate / deprecate) description: Operator safety valve (plan SG7). Activation is policy-gated config_mutation (S4). x-required-scope: operator parameters: - $ref: '#/components/parameters/IfMatch' requestBody: required: true content: application/json: schema: type: object properties: status: type: string enum: - validated - active - deprecated - invalidated quarantined: type: boolean note: type: string responses: '200': description: Updated content: application/json: schema: $ref: '#/components/schemas/Pattern' '202': $ref: '#/components/responses/PendingApproval' default: $ref: '#/components/responses/Problem' /skills: get: tags: - learning operationId: listSkills summary: List skills (latest version per skill) x-required-scope: viewer parameters: - name: applies_to in: query schema: type: string - name: action in: query schema: type: string - name: status in: query schema: type: string - $ref: '#/components/parameters/Cursor' - $ref: '#/components/parameters/Limit' responses: '200': description: Skills content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Skill' next_cursor: type: string nullable: true default: $ref: '#/components/responses/Problem' /skills/{id}: parameters: - $ref: '#/components/parameters/EntityId' patch: tags: - learning operationId: patchSkill summary: Transition a skill or pin a version x-required-scope: operator parameters: - $ref: '#/components/parameters/IfMatch' requestBody: required: true content: application/json: schema: type: object properties: status: type: string enum: - tested - active - deprecated pinned_version: type: integer note: type: string responses: '200': description: Updated content: application/json: schema: $ref: '#/components/schemas/Skill' '202': $ref: '#/components/responses/PendingApproval' default: $ref: '#/components/responses/Problem' /skills/{id}/versions: parameters: - $ref: '#/components/parameters/EntityId' get: tags: - learning operationId: listSkillVersions summary: Version history of a skill x-required-scope: viewer responses: '200': description: Versions content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Skill' default: $ref: '#/components/responses/Problem' /policy/risk-classes: get: tags: - policy operationId: listRiskClasses summary: List risk classes x-required-scope: viewer responses: '200': description: Risk classes content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/RiskClass' default: $ref: '#/components/responses/Problem' /policy/approval-rules: get: tags: - policy operationId: listApprovalRules summary: List approval rules x-required-scope: viewer responses: '200': description: Rules content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/ApprovalRule' default: $ref: '#/components/responses/Problem' post: tags: - policy operationId: createApprovalRule summary: Propose a new approval rule (dual-control) description: Creates a policy-change approval; the rule applies only after operator approval (plan S3). x-required-scope: operator parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ApprovalRuleCreate' responses: '202': $ref: '#/components/responses/PendingApproval' default: $ref: '#/components/responses/Problem' /policy/approval-rules/{id}: parameters: - $ref: '#/components/parameters/EntityId' patch: tags: - policy operationId: patchApprovalRule summary: Propose a rule change (dual-control) x-required-scope: operator parameters: - $ref: '#/components/parameters/IfMatch' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ApprovalRuleCreate' responses: '202': $ref: '#/components/responses/PendingApproval' default: $ref: '#/components/responses/Problem' /policy/autonomy: get: tags: - policy operationId: getAutonomySettings summary: Autonomy settings (kill-switch, never-auto-act list) x-required-scope: viewer responses: '200': description: Settings content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/AutonomySetting' default: $ref: '#/components/responses/Problem' patch: tags: - policy operationId: patchAutonomySettings summary: Propose autonomy changes (dual-control; kill-switch OFF is immediate) description: 'Raising autonomy is dual-controlled (202 + approval). Lowering it — setting `global.auto_act: "off"` or adding a never_auto_act key — applies immediately (200): the kill-switch must never wait for an approval. ' x-required-scope: operator requestBody: required: true content: application/json: schema: type: object additionalProperties: type: string responses: '200': description: Applied (restriction) content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/AutonomySetting' '202': $ref: '#/components/responses/PendingApproval' default: $ref: '#/components/responses/Problem' /knowledge/search: get: tags: - knowledge operationId: searchKnowledge summary: Full-text search over knowledge entities (documents, runbooks) x-required-scope: viewer parameters: - name: q in: query required: true schema: type: string - $ref: '#/components/parameters/Limit' responses: '200': description: Hits content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/KnowledgeHit' default: $ref: '#/components/responses/Problem' /knowledge/{entity_id}: parameters: - name: entity_id in: path required: true schema: type: string get: tags: - knowledge operationId: getEntityKnowledge summary: All documents/runbooks linked to an entity x-required-scope: viewer responses: '200': description: Linked knowledge content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/KnowledgeHit' default: $ref: '#/components/responses/Problem' /metrics: get: tags: - observability operationId: queryMetrics summary: Query time-series metrics x-required-scope: viewer parameters: - name: entity_id in: query schema: type: string - name: metric in: query schema: type: array items: type: string style: form explode: true - name: rollup in: query schema: type: string enum: - raw - 1h - 1d - auto default: auto - $ref: '#/components/parameters/FromTime' - $ref: '#/components/parameters/ToTime' responses: '200': description: Metric series content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/MetricSeries' default: $ref: '#/components/responses/Problem' /trends/{entity_id}: parameters: - name: entity_id in: path required: true schema: type: string get: tags: - observability operationId: getTrends summary: Trend analysis for all metrics on an entity x-required-scope: viewer parameters: - $ref: '#/components/parameters/FromTime' - $ref: '#/components/parameters/ToTime' responses: '200': description: Trends content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Trend' default: $ref: '#/components/responses/Problem' /audit: get: tags: - observability operationId: queryAudit summary: Audit log x-required-scope: operator parameters: - name: actor_type in: query schema: type: string - name: actor_id in: query schema: type: string - name: entity_id in: query schema: type: string - name: action in: query schema: type: string - name: correlation_id in: query schema: type: string - $ref: '#/components/parameters/FromTime' - $ref: '#/components/parameters/ToTime' - $ref: '#/components/parameters/Cursor' - $ref: '#/components/parameters/Limit' responses: '200': description: Audit entries (ts DESC) content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/AuditEntry' next_cursor: type: string nullable: true default: $ref: '#/components/responses/Problem' /events: get: tags: - observability operationId: queryEvents summary: Historical events x-required-scope: viewer parameters: - name: type in: query schema: type: string - name: entity_id in: query schema: type: string - name: severity in: query schema: type: string - name: correlation_id in: query schema: type: string - $ref: '#/components/parameters/FromTime' - $ref: '#/components/parameters/ToTime' - $ref: '#/components/parameters/Cursor' - $ref: '#/components/parameters/Limit' responses: '200': description: Events (ts DESC) content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Event' next_cursor: type: string nullable: true default: $ref: '#/components/responses/Problem' /events/stream: get: tags: - observability operationId: streamEvents summary: Live event stream (SSE) description: 'Server-Sent Events. Each event''s `id` is the event row id (resume with `Last-Event-ID`), `event` is the event type, `data` is the JSON Event object. Heartbeat comments every 15s. Best-effort delivery — bounded per-subscriber buffer, drop-oldest (plan P6); use GET /events to backfill. ' x-required-scope: viewer parameters: - name: type in: query schema: type: string description: Filter by event type prefix - name: Last-Event-ID in: header schema: type: string responses: '200': description: SSE stream content: text/event-stream: schema: type: string default: $ref: '#/components/responses/Problem' /agent-activity: get: tags: - observability operationId: queryAgentActivity summary: Agent behavior log x-required-scope: viewer parameters: - name: agent_id in: query schema: type: string - name: activity_type in: query schema: type: string - name: entity_id in: query schema: type: string - $ref: '#/components/parameters/FromTime' - $ref: '#/components/parameters/ToTime' - $ref: '#/components/parameters/Cursor' - $ref: '#/components/parameters/Limit' responses: '200': description: Activity entries content: application/json: schema: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/AgentActivity' next_cursor: type: string nullable: true default: $ref: '#/components/responses/Problem' /health: get: tags: - observability operationId: getFleetHealth summary: Fleet health summary with trend indicators x-required-scope: viewer responses: '200': description: Health summary content: application/json: schema: $ref: '#/components/schemas/HealthSummary' default: $ref: '#/components/responses/Problem' /dashboard/summary: get: tags: - observability operationId: getDashboardSummary summary: One-round-trip overview for the control room home page x-required-scope: viewer responses: '200': description: Dashboard summary content: application/json: schema: $ref: '#/components/schemas/DashboardSummary' default: $ref: '#/components/responses/Problem' /export: get: tags: - system operationId: exportSeeds summary: Regenerate seed YAMLs from current DB state (DR / version control) x-required-scope: operator responses: '200': description: Seed bundle content: application/json: schema: type: object required: - ontology - inventory - policy properties: ontology: type: string description: YAML document inventory: type: string description: YAML document policy: type: string description: YAML document default: $ref: '#/components/responses/Problem' components: securitySchemes: bearerAuth: type: http scheme: bearer description: 'Operator/viewer: Authentik OIDC JWT (validated in-API — Caddy forward-auth is defense-in-depth, not the source of truth). Agent: static bearer token from Infisical (scope `agent`). ' parameters: EntityId: name: id in: path required: true schema: type: string description: UUID or slug (e.g. `host:hubris`) Cursor: name: cursor in: query schema: type: string description: Opaque cursor from a previous response's next_cursor Limit: name: limit in: query schema: type: integer default: 50 maximum: 200 minimum: 1 FromTime: name: from in: query schema: type: string format: date-time ToTime: name: to in: query schema: type: string format: date-time IfMatch: name: If-Match in: header required: true schema: type: string description: ETag from a prior GET; 412 on version mismatch IdempotencyKey: name: Idempotency-Key in: header schema: type: string maxLength: 128 description: Client-generated key; replays within 24h return the original response EntitySlug: name: slug in: path required: true schema: type: string description: Entity slug (e.g. `ws:mac-mini`, `lxc:caddy`) headers: ETag: schema: type: string description: Resource version for If-Match responses: Problem: description: Error (RFC 9457) content: application/problem+json: schema: $ref: '#/components/schemas/Problem' PendingApproval: description: Change staged; a meta-approval was created (dual-control) content: application/json: schema: $ref: '#/components/schemas/Approval' SignalUpdated: description: Updated signal content: application/json: schema: $ref: '#/components/schemas/Signal' schemas: Problem: type: object required: - title - status properties: type: type: string format: uri default: about:blank title: type: string status: type: integer detail: type: string instance: type: string errors: type: array description: Field-level validation errors (422) items: type: object required: - field - reason properties: field: type: string reason: type: string Entity: type: object required: - id - slug - type - name - version - created_at - updated_at properties: id: type: string format: uuid slug: type: string example: host:hubris type: type: string name: type: string state: type: string nullable: true attributes: type: object maintenance_until: type: string format: date-time nullable: true version: type: integer created_at: type: string format: date-time updated_at: type: string format: date-time health: type: string description: last observed health, when the entity is monitored nullable: true enum: - healthy - degraded - down - unknown - stale last_check_at: type: string format: date-time nullable: true description: when health was last observed EntityCreate: type: object required: - slug - type - name properties: slug: type: string type: type: string description: Must be a non-abstract entity type name: type: string state: type: string description: Defaults to the lifecycle's default_state attributes: type: object description: Validated against the type's attribute_schema EntityPatch: type: object description: At least one of the fields must be present. properties: name: type: string state: type: string description: Target lifecycle state (transition validated) attributes: type: object description: Merged; validated against attribute_schema maintenance_until: type: string format: date-time nullable: true Relationship: type: object required: - source - target - type - valid_from properties: source: type: string description: Slug of source entity target: type: string description: Slug of target entity type: type: string attributes: type: object nullable: true valid_from: type: string format: date-time valid_to: type: string format: date-time nullable: true RelationshipCreate: type: object required: - source - target - type properties: source: type: string description: UUID or slug target: type: string description: UUID or slug type: type: string attributes: type: object GraphView: type: object required: - nodes - edges properties: nodes: type: array items: $ref: '#/components/schemas/Entity' edges: type: array items: $ref: '#/components/schemas/Relationship' truncated: type: boolean description: True if node cap was hit health: type: object description: entity id -> health, present when include=status was requested additionalProperties: type: string enum: - healthy - degraded - down - unknown - stale EntityType: type: object required: - name - domain - layer - is_abstract - status properties: name: type: string parent_type: type: string nullable: true is_abstract: type: boolean domain: type: string layer: type: string enum: - meta - infrastructure - governance - cognition description: type: string lifecycle_id: type: string nullable: true attribute_schema: type: object description: JSON Schema nullable: true schema_version: type: integer status: type: string enum: - active - deprecated version: type: integer EntityTypeCreate: type: object required: - name - domain - layer properties: name: type: string parent_type: type: string is_abstract: type: boolean default: false domain: type: string layer: type: string enum: - infrastructure - governance - cognition description: type: string lifecycle_id: type: string attribute_schema: type: object EntityTypePatch: type: object properties: description: type: string attribute_schema: type: object status: type: string enum: - active - deprecated RelationshipType: type: object required: - name - source_type - target_type - cardinality properties: name: type: string inverse: type: string nullable: true source_type: type: string description: May be abstract target_type: type: string description: May be abstract cardinality: type: string enum: - one-to-one - one-to-many - many-to-one - many-to-many description: type: string LifecycleDef: type: object required: - id - states - default_state - transitions properties: id: type: string states: type: array items: type: string default_state: type: string terminal_states: type: array items: type: string transitions: type: object description: '{from: {to: {requires: [named-check, ...]}}}' Signal: type: object required: - id - slug - kind - severity - state - occurrence_count - first_seen_at - last_seen_at properties: id: type: string format: uuid slug: type: string kind: type: string example: service-down severity: type: string enum: - info - warning - critical state: type: string enum: - raised - acknowledged - acting - muted - resolved - failed target: type: string description: Slug of the entity this concerns nullable: true check_id: type: string nullable: true evidence: type: string nullable: true likely_cause: type: string nullable: true occurrence_count: type: integer flap_count: type: integer hold_down_until: type: string format: date-time nullable: true mute_until: type: string format: date-time nullable: true first_seen_at: type: string format: date-time last_seen_at: type: string format: date-time Check: type: object required: - id - slug - kind - interval_s - timeout_s - enabled - version properties: id: type: string format: uuid slug: type: string kind: type: string enum: - http - tcp - disk - cert-expiry - drift - ping - ssh-script target: type: string description: Entity slug (instance-scoped) nullable: true target_type: type: string description: Entity type (type-scoped) nullable: true config: type: object description: Validated per-kind interval_s: type: integer timeout_s: type: integer zone: type: string nullable: true enabled: type: boolean version: type: integer last_health: type: string description: >- This check's own most recent verdict. An entity's health is the worst of these across its enabled checks, so this is what explains *why* an entity is degraded. Null until the check first runs. nullable: true enum: - healthy - degraded - down - unknown last_run_at: type: string format: date-time description: When this check last executed. Null = never run. nullable: true CheckCreate: type: object required: - slug - kind properties: slug: type: string kind: type: string enum: - http - tcp - disk - cert-expiry - drift - ping - ssh-script target: type: string target_type: type: string config: type: object interval_s: type: integer default: 600 timeout_s: type: integer default: 10 zone: type: string enabled: type: boolean default: true CheckPatch: type: object properties: config: type: object interval_s: type: integer timeout_s: type: integer enabled: type: boolean Execution: type: object required: - id - slug - action - risk_class - status - correlation_id - created_at properties: id: type: string format: uuid slug: type: string target: type: string nullable: true action: type: string risk_class: type: string status: type: string enum: - proposed - approved - auto_approved - denied - expired - executing - verifying - verified - failed - timed_out - cancelled - rolled_back - rollback_failed classification_id: type: string nullable: true signal_id: type: string nullable: true approval_id: type: string nullable: true agent_id: type: string nullable: true skill_id: type: string nullable: true skill_version: type: integer nullable: true params: type: object description: Skill params (validated against params_schema) result: type: object nullable: true duration_ms: type: integer nullable: true verified: type: boolean correlation_id: type: string started_at: type: string format: date-time nullable: true completed_at: type: string format: date-time nullable: true created_at: type: string format: date-time ExecutionRequest: type: object required: - target - action properties: target: type: string description: Entity UUID or slug action: type: string example: restart params: type: object signal_id: type: string description: Signal that motivated this (optional) reason: type: string Classification: type: object required: - id - action - risk_class - route - reasoning - correlation_id - created_at properties: id: type: string format: uuid signal_id: type: string nullable: true target: type: string nullable: true action: type: string recommended_action: type: object nullable: true risk_class: type: string route: type: string enum: - auto-act - escalate - hold blast_radius: type: array items: type: string pattern_confidence: type: number nullable: true skill_id: type: string nullable: true autonomy_check: type: string reasoning: type: object correlation_id: type: string created_at: type: string format: date-time Approval: type: object required: - id - slug - action - risk_class - kind - status - expires_at - created_at properties: id: type: string format: uuid slug: type: string subject: type: string description: Entity slug the approval concerns nullable: true action: type: string risk_class: type: string kind: type: string enum: - execution - policy-change - pattern-activation payload: type: object description: e.g. proposed policy diff nullable: true status: type: string enum: - pending - approved - denied - expired - revoked expires_at: type: string format: date-time decided_at: type: string format: date-time nullable: true decided_by: type: string nullable: true created_at: type: string format: date-time Pattern: type: object required: - id - slug - applies_type - action - pattern - confidence - evidence_count - status - version properties: id: type: string format: uuid slug: type: string applies_type: type: string action: type: string pattern: type: string confidence: type: number description: Wilson lower bound, capped by evidence_count/5 evidence_count: type: integer success_count: type: integer failure_count: type: integer status: type: string enum: - hypothesized - validated - active - deprecated - invalidated quarantined: type: boolean version: type: integer last_validated_at: type: string format: date-time nullable: true Skill: type: object required: - id - slug - name - version - action - status - procedure properties: id: type: string format: uuid slug: type: string name: type: string version: type: integer action: type: string applies_type: type: string nullable: true procedure: type: object description: Structured steps/verify/rollback/params (plan R3-9) required: - steps - verify properties: params_schema: type: object steps: type: array items: $ref: '#/components/schemas/SkillStep' verify: type: array items: $ref: '#/components/schemas/SkillStep' rollback: type: array items: $ref: '#/components/schemas/SkillStep' expected_duration_s: type: integer known_failure_modes: type: array items: type: string pattern_ids: type: array items: type: string status: type: string enum: - drafted - tested - active - refined - failed - deprecated success_rate: type: number nullable: true changed_by: type: string nullable: true change_reason: type: string nullable: true last_used_at: type: string format: date-time nullable: true SkillStep: type: object required: - runner - command properties: name: type: string runner: type: string enum: - ssh - http - internal target: type: string description: Go template over params command: type: string description: Go template over params timeout_s: type: integer default: 60 expect: type: object properties: exit_code: type: integer stdout_contains: type: string retry: type: object properties: attempts: type: integer delay_s: type: integer RiskClass: type: object required: - name - approval_required - autonomy_allowed properties: name: type: string description: type: string approval_required: type: string enum: - none - operator - operator_confirmed autonomy_allowed: type: boolean ApprovalRule: type: object required: - id - action - risk_class - autonomy_level - version properties: id: type: string format: uuid entity_type: type: string description: May be abstract (inherits down) nullable: true action: type: string risk_class: type: string autonomy_level: type: string enum: - auto - escalate - never scope_entity: type: string description: Entity slug for per-entity overrides nullable: true version: type: integer ApprovalRuleCreate: type: object required: - action - risk_class - autonomy_level properties: entity_type: type: string action: type: string risk_class: type: string autonomy_level: type: string enum: - auto - escalate - never scope_entity: type: string AutonomySetting: type: object required: - key - value - version properties: key: type: string example: global.auto_act value: type: string version: type: integer updated_at: type: string format: date-time KnowledgeHit: type: object required: - id - slug - type - title properties: id: type: string format: uuid slug: type: string type: type: string enum: - document - runbook - investigation title: type: string source_path: type: string nullable: true snippet: type: string description: Highlighted match context nullable: true linked_entities: type: array items: type: string rank: type: number nullable: true MetricSeries: type: object required: - entity_id - metric - rollup - samples properties: entity_id: type: string metric: type: string rollup: type: string enum: - raw - 1h - 1d samples: type: array items: type: object required: - ts properties: ts: type: string format: date-time value: type: number description: Raw sample value nullable: true avg: type: number nullable: true min: type: number nullable: true max: type: number nullable: true count: type: integer nullable: true trend: $ref: '#/components/schemas/Trend' Trend: type: object required: - metric - direction properties: metric: type: string direction: type: string enum: - improving - degrading - stable - unknown slope: type: number description: Linear fit per day nullable: true anomaly: type: boolean forecast: type: number description: Simple linear projection, 7d out nullable: true AuditEntry: type: object required: - id - ts - actor_type - action properties: id: type: integer ts: type: string format: date-time actor_type: type: string enum: - agent - operator - system - scheduler actor_id: type: string nullable: true action: type: string entity_id: type: string nullable: true method: type: string nullable: true path: type: string nullable: true status_code: type: integer nullable: true detail: type: object source_ip: type: string nullable: true correlation_id: type: string nullable: true Event: type: object required: - id - ts - type - severity - source properties: id: type: integer ts: type: string format: date-time type: type: string example: signal.raised entity_id: type: string nullable: true severity: type: string enum: - info - warning - critical source: type: string data: type: object correlation_id: type: string nullable: true AgentActivity: type: object required: - id - ts - agent_id - activity_type properties: id: type: integer ts: type: string format: date-time agent_id: type: string session_id: type: string nullable: true activity_type: type: string enum: - tool_call - reasoning - decision - mcp_query - escalation tool_name: type: string nullable: true entity_id: type: string nullable: true input_summary: type: string nullable: true output_summary: type: string nullable: true duration_ms: type: integer nullable: true token_count: type: integer nullable: true success: type: boolean nullable: true correlation_id: type: string nullable: true HealthSummary: type: object required: - summary - entities properties: summary: type: object required: - healthy - degraded - down - unknown properties: healthy: type: integer degraded: type: integer down: type: integer unknown: type: integer stale: type: integer description: last observation older than the check's expected cadence entities: type: array items: type: object required: - slug - type - health properties: slug: type: string type: type: string health: type: string enum: - healthy - degraded - down - unknown - stale trend: type: string enum: - improving - degrading - stable - unknown nullable: true last_check_at: type: string format: date-time nullable: true DashboardSummary: type: object required: - entities_by_type - entities_by_state - health - signals_by_severity - approvals_pending - executions_by_state - event_rate properties: entities_by_type: type: object description: entity counts keyed by type additionalProperties: type: integer entities_by_state: type: object description: entity counts keyed by state additionalProperties: type: integer health: type: object required: - healthy - degraded - down - unknown properties: healthy: type: integer degraded: type: integer down: type: integer unknown: type: integer stale: type: integer description: last observation older than the check's expected cadence signals_by_severity: type: object description: open (non-resolved) signal counts keyed by severity additionalProperties: type: integer approvals_pending: type: integer executions_by_state: type: object description: execution counts keyed by state, last 24h additionalProperties: type: integer event_rate: type: array description: event counts bucketed by 5-minute interval, most recent last items: type: object required: - bucket - count properties: bucket: type: string format: date-time count: type: integer EnrollRequest: type: object required: - slug properties: slug: type: string description: Entity slug (e.g. ws:new-laptop) hostname: type: string description: Actual hostname of the enrolling machine mesh_ip: type: string description: Source mesh IP for identity validation EnrollResponse: type: object required: - age_public_key - age_private_key - infisical_client_id - infisical_client_secret properties: age_public_key: type: string description: age1... public key for SOPS recipients age_private_key: type: string description: AGE-SECRET-KEY-... for local decryption infisical_client_id: type: string description: Infisical UniversalAuth client ID infisical_client_secret: type: string description: Infisical UniversalAuth client secret machine_identity_token: type: string description: Infisical machine identity access token ClientContext: type: object required: - version properties: version: type: integer description: Monotonic context version number agent_files_changed: type: array items: type: string description: Paths of agent instruction files that changed sops_config_changed: type: boolean description: True if .sops.yaml recipients changed tools_changed: type: array items: type: string description: Paths of tools/*.setup.sh that changed since: type: string format: date-time description: Timestamp for the next poll request ClientSecrets: type: object required: - keys properties: keys: type: array items: type: string description: Infisical secret keys accessible to this client ProvisionRequest: type: object required: - slug - type - name - host properties: slug: type: string description: e.g. lxc:jellyfin type: type: string description: Must be lxc, vm, or docker-container name: type: string description: Human-readable name host: type: string description: Slug of the Proxmox host (e.g. host:hubris) attributes: type: object description: VMID, cores, ram_mb, disk_gb, ip, template, mounts, services ProvisionResponse: type: object required: - entity - execution_id properties: entity: $ref: '#/components/schemas/Entity' execution_id: type: string format: uuid description: Execution ID to track provisioning progress ProvisionStatus: type: object required: - slug - state - steps properties: slug: type: string state: type: string description: Current entity state steps: type: array items: type: object required: - step - status properties: step: type: string status: type: string enum: - pending - running - ok - failed - skipped error_message: type: string nullable: true started_at: type: string format: date-time nullable: true finished_at: type: string format: date-time nullable: true error: type: string nullable: true description: Overall error if provisioning failed