From ff20b33b75ab5182ee913867bdb3f5d949f01fd2 Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Mon, 23 Mar 2026 16:21:24 -0400 Subject: [PATCH] docs: add OpenAPI 3.1 spec covering all 78 API operations Generate api/openapi.yaml from handler and domain source code. Covers all 76 endpoints under /api/v1/ plus /health and /ready (78 total). Includes full request/response schemas, domain model definitions, enum types (CertificateStatus, JobType, RevocationReason, etc.), reusable pagination envelope, error responses, and common parameters. This spec serves as the contract for the upcoming MCP server (M18a) and enables Swagger/Redocly interactive documentation immediately. Co-Authored-By: Claude Opus 4.6 --- README.md | 2 +- api/openapi.yaml | 2580 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2581 insertions(+), 1 deletion(-) create mode 100644 api/openapi.yaml diff --git a/README.md b/README.md index 14f3f1d..d52e45c 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Docker Compose overrides these for the demo stack (see `deploy/docker-compose.ym ## API Overview -All endpoints are under `/api/v1/` and return JSON. List endpoints support pagination (`?page=1&per_page=50`). +All endpoints are under `/api/v1/` and return JSON. List endpoints support pagination (`?page=1&per_page=50`). Full request/response schemas are available in the [OpenAPI 3.1 spec](api/openapi.yaml). ### Certificates ``` diff --git a/api/openapi.yaml b/api/openapi.yaml new file mode 100644 index 0000000..a8186ac --- /dev/null +++ b/api/openapi.yaml @@ -0,0 +1,2580 @@ +openapi: 3.1.0 +info: + title: certctl API + description: | + Certificate lifecycle management platform API. Manages certificates, issuers, + deployment targets, agents, jobs, policies, profiles, teams, owners, agent groups, + audit events, notifications, and observability metrics. + + All endpoints under `/api/v1/` require authentication by default (configurable via + `CERTCTL_AUTH_TYPE`). Use `Bearer {api_key}` in the Authorization header. + + Paginated list endpoints accept `page` (default 1) and `per_page` (default 50, max 500) + query parameters and return a standard envelope with `data`, `total`, `page`, and `per_page`. + version: 2.0.0 + license: + name: BSL 1.1 + url: https://github.com/shankar0123/certctl/blob/master/LICENSE + +servers: + - url: http://localhost:8080 + description: Local development + - url: http://localhost:8443 + description: Docker Compose demo + +security: + - bearerAuth: [] + +tags: + - name: Certificates + description: Certificate lifecycle — CRUD, versions, renewal, deployment, revocation + - name: CRL & OCSP + description: Certificate revocation list and OCSP responder + - name: Issuers + description: CA issuer connector management (Local CA, ACME, step-ca) + - name: Targets + description: Deployment target management (NGINX, Apache, HAProxy, F5, IIS) + - name: Agents + description: Agent registration, heartbeat, CSR submission, work polling + - name: Jobs + description: Job queue — issuance, renewal, deployment, validation + - name: Policies + description: Policy rules and violation tracking + - name: Profiles + description: Certificate enrollment profiles with crypto constraints + - name: Teams + description: Team management for ownership grouping + - name: Owners + description: Certificate owner management with email routing + - name: Agent Groups + description: Dynamic agent grouping by OS, architecture, IP CIDR, version + - name: Audit + description: Immutable audit trail + - name: Notifications + description: Notification events (expiration, renewal, deployment, revocation) + - name: Stats + description: Dashboard statistics and aggregations + - name: Metrics + description: System metrics (gauges, counters, uptime) + - name: Health + description: Health and readiness probes, auth info + +paths: + # ─── Health & Auth ─────────────────────────────────────────────────── + /health: + get: + tags: [Health] + summary: Health check + security: [] + operationId: getHealth + responses: + "200": + description: Server is healthy + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: healthy + + /ready: + get: + tags: [Health] + summary: Readiness check + security: [] + operationId: getReady + responses: + "200": + description: Server is ready + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: ready + + /api/v1/auth/info: + get: + tags: [Health] + summary: Auth configuration info + description: Returns auth mode. Served without auth so GUI can detect auth requirements before login. + security: [] + operationId: getAuthInfo + responses: + "200": + description: Auth configuration + content: + application/json: + schema: + type: object + properties: + auth_type: + type: string + enum: [api-key, jwt, none] + required: + type: boolean + + /api/v1/auth/check: + get: + tags: [Health] + summary: Validate credentials + description: Returns 200 if auth credentials are valid, 401 otherwise. + operationId: checkAuth + responses: + "200": + description: Authenticated + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: authenticated + "401": + description: Unauthorized + + # ─── Certificates ──────────────────────────────────────────────────── + /api/v1/certificates: + get: + tags: [Certificates] + summary: List certificates + operationId: listCertificates + parameters: + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/per_page" + - name: status + in: query + schema: + $ref: "#/components/schemas/CertificateStatus" + - name: environment + in: query + schema: + type: string + - name: owner_id + in: query + schema: + type: string + - name: team_id + in: query + schema: + type: string + - name: issuer_id + in: query + schema: + type: string + responses: + "200": + description: Paginated list of certificates + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/ManagedCertificate" + "500": + $ref: "#/components/responses/InternalError" + post: + tags: [Certificates] + summary: Create certificate + operationId: createCertificate + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ManagedCertificate" + responses: + "201": + description: Certificate created + content: + application/json: + schema: + $ref: "#/components/schemas/ManagedCertificate" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/certificates/{id}: + get: + tags: [Certificates] + summary: Get certificate + operationId: getCertificate + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Certificate details + content: + application/json: + schema: + $ref: "#/components/schemas/ManagedCertificate" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + put: + tags: [Certificates] + summary: Update certificate + operationId: updateCertificate + parameters: + - $ref: "#/components/parameters/resourceId" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ManagedCertificate" + responses: + "200": + description: Certificate updated + content: + application/json: + schema: + $ref: "#/components/schemas/ManagedCertificate" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + delete: + tags: [Certificates] + summary: Archive certificate + operationId: archiveCertificate + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "204": + description: Certificate archived + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/certificates/{id}/versions: + get: + tags: [Certificates] + summary: List certificate versions + operationId: listCertificateVersions + parameters: + - $ref: "#/components/parameters/resourceId" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/per_page" + responses: + "200": + description: Paginated list of certificate versions + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/CertificateVersion" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/certificates/{id}/renew: + post: + tags: [Certificates] + summary: Trigger certificate renewal + operationId: triggerRenewal + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "202": + description: Renewal triggered + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/certificates/{id}/deploy: + post: + tags: [Certificates] + summary: Trigger certificate deployment + operationId: triggerDeployment + parameters: + - $ref: "#/components/parameters/resourceId" + requestBody: + content: + application/json: + schema: + type: object + properties: + target_id: + type: string + description: Optional specific target ID + responses: + "202": + description: Deployment triggered + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/certificates/{id}/revoke: + post: + tags: [Certificates] + summary: Revoke certificate + description: | + Revokes a certificate with an optional RFC 5280 reason code. Records revocation in + cert inventory, audit log, and certificate_revocations table. Best-effort issuer notification. + operationId: revokeCertificate + parameters: + - $ref: "#/components/parameters/resourceId" + requestBody: + content: + application/json: + schema: + type: object + properties: + reason: + $ref: "#/components/schemas/RevocationReason" + responses: + "200": + description: Certificate revoked + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + + # ─── CRL & OCSP ───────────────────────────────────────────────────── + /api/v1/crl: + get: + tags: [CRL & OCSP] + summary: Get JSON CRL + description: Returns all revoked certificates in JSON format. + operationId: getCRL + responses: + "200": + description: JSON CRL + content: + application/json: + schema: + type: object + properties: + version: + type: integer + example: 1 + entries: + type: array + items: + type: object + properties: + serial_number: + type: string + revocation_date: + type: string + format: date-time + revocation_reason: + type: string + total: + type: integer + generated_at: + type: string + format: date-time + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/crl/{issuer_id}: + get: + tags: [CRL & OCSP] + summary: Get DER-encoded X.509 CRL + description: Returns a proper DER-encoded CRL signed by the issuing CA. 24-hour validity. + operationId: getDERCRL + parameters: + - name: issuer_id + in: path + required: true + schema: + type: string + responses: + "200": + description: DER-encoded CRL + content: + application/pkix-crl: + schema: + type: string + format: binary + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + "501": + description: Issuer does not support CRL generation + + /api/v1/ocsp/{issuer_id}/{serial}: + get: + tags: [CRL & OCSP] + summary: OCSP responder + description: Returns signed OCSP response (good/revoked/unknown) for the given serial number. + operationId: handleOCSP + parameters: + - name: issuer_id + in: path + required: true + schema: + type: string + - name: serial + in: path + required: true + description: Hex-encoded certificate serial number + schema: + type: string + responses: + "200": + description: OCSP response + content: + application/ocsp-response: + schema: + type: string + format: binary + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + "501": + description: Issuer does not support OCSP + + # ─── Issuers ───────────────────────────────────────────────────────── + /api/v1/issuers: + get: + tags: [Issuers] + summary: List issuers + operationId: listIssuers + parameters: + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/per_page" + responses: + "200": + description: Paginated list of issuers + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Issuer" + "500": + $ref: "#/components/responses/InternalError" + post: + tags: [Issuers] + summary: Create issuer + operationId: createIssuer + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Issuer" + responses: + "201": + description: Issuer created + content: + application/json: + schema: + $ref: "#/components/schemas/Issuer" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/issuers/{id}: + get: + tags: [Issuers] + summary: Get issuer + operationId: getIssuer + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Issuer details + content: + application/json: + schema: + $ref: "#/components/schemas/Issuer" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + put: + tags: [Issuers] + summary: Update issuer + operationId: updateIssuer + parameters: + - $ref: "#/components/parameters/resourceId" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Issuer" + responses: + "200": + description: Issuer updated + content: + application/json: + schema: + $ref: "#/components/schemas/Issuer" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + delete: + tags: [Issuers] + summary: Delete issuer + operationId: deleteIssuer + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "204": + description: Issuer deleted + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/issuers/{id}/test: + post: + tags: [Issuers] + summary: Test issuer connection + operationId: testIssuerConnection + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Connection successful + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + # ─── Targets ───────────────────────────────────────────────────────── + /api/v1/targets: + get: + tags: [Targets] + summary: List targets + operationId: listTargets + parameters: + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/per_page" + responses: + "200": + description: Paginated list of targets + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/DeploymentTarget" + "500": + $ref: "#/components/responses/InternalError" + post: + tags: [Targets] + summary: Create target + operationId: createTarget + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/DeploymentTarget" + responses: + "201": + description: Target created + content: + application/json: + schema: + $ref: "#/components/schemas/DeploymentTarget" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/targets/{id}: + get: + tags: [Targets] + summary: Get target + operationId: getTarget + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Target details + content: + application/json: + schema: + $ref: "#/components/schemas/DeploymentTarget" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + put: + tags: [Targets] + summary: Update target + operationId: updateTarget + parameters: + - $ref: "#/components/parameters/resourceId" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/DeploymentTarget" + responses: + "200": + description: Target updated + content: + application/json: + schema: + $ref: "#/components/schemas/DeploymentTarget" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + delete: + tags: [Targets] + summary: Delete target + operationId: deleteTarget + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "204": + description: Target deleted + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + # ─── Agents ────────────────────────────────────────────────────────── + /api/v1/agents: + get: + tags: [Agents] + summary: List agents + operationId: listAgents + parameters: + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/per_page" + responses: + "200": + description: Paginated list of agents + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Agent" + "500": + $ref: "#/components/responses/InternalError" + post: + tags: [Agents] + summary: Register agent + operationId: registerAgent + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Agent" + responses: + "201": + description: Agent registered + content: + application/json: + schema: + $ref: "#/components/schemas/Agent" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/agents/{id}: + get: + tags: [Agents] + summary: Get agent + operationId: getAgent + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Agent details + content: + application/json: + schema: + $ref: "#/components/schemas/Agent" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/agents/{id}/heartbeat: + post: + tags: [Agents] + summary: Agent heartbeat + description: Reports agent liveness and metadata (OS, architecture, IP, version). + operationId: agentHeartbeat + parameters: + - $ref: "#/components/parameters/resourceId" + requestBody: + content: + application/json: + schema: + type: object + properties: + version: + type: string + hostname: + type: string + os: + type: string + architecture: + type: string + ip_address: + type: string + responses: + "200": + description: Heartbeat recorded + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/agents/{id}/csr: + post: + tags: [Agents] + summary: Submit CSR + description: Agent submits a PEM-encoded CSR for signing. Used in agent keygen mode. + operationId: agentSubmitCSR + parameters: + - $ref: "#/components/parameters/resourceId" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [csr_pem] + properties: + csr_pem: + type: string + description: PEM-encoded certificate signing request + certificate_id: + type: string + responses: + "202": + description: CSR accepted + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/agents/{id}/certificates/{cert_id}: + get: + tags: [Agents] + summary: Pick up signed certificate + description: Agent retrieves the signed certificate PEM after CSR signing completes. + operationId: agentPickupCertificate + parameters: + - $ref: "#/components/parameters/resourceId" + - name: cert_id + in: path + required: true + schema: + type: string + responses: + "200": + description: Certificate PEM + content: + application/json: + schema: + type: object + properties: + certificate_pem: + type: string + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/agents/{id}/work: + get: + tags: [Agents] + summary: Get pending work + description: Returns pending deployment and AwaitingCSR jobs for the agent. + operationId: agentGetWork + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Work items + content: + application/json: + schema: + type: object + properties: + jobs: + type: array + items: + $ref: "#/components/schemas/WorkItem" + count: + type: integer + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/agents/{id}/jobs/{job_id}/status: + post: + tags: [Agents] + summary: Report job status + description: Agent reports completion or failure of an assigned job. + operationId: agentReportJobStatus + parameters: + - $ref: "#/components/parameters/resourceId" + - name: job_id + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [status] + properties: + status: + type: string + error: + type: string + responses: + "200": + description: Status updated + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + # ─── Jobs ──────────────────────────────────────────────────────────── + /api/v1/jobs: + get: + tags: [Jobs] + summary: List jobs + operationId: listJobs + parameters: + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/per_page" + - name: status + in: query + schema: + $ref: "#/components/schemas/JobStatus" + - name: type + in: query + schema: + $ref: "#/components/schemas/JobType" + responses: + "200": + description: Paginated list of jobs + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Job" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/jobs/{id}: + get: + tags: [Jobs] + summary: Get job + operationId: getJob + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Job details + content: + application/json: + schema: + $ref: "#/components/schemas/Job" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/jobs/{id}/cancel: + post: + tags: [Jobs] + summary: Cancel job + operationId: cancelJob + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Job cancelled + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/jobs/{id}/approve: + post: + tags: [Jobs] + summary: Approve job + description: Approves a job in AwaitingApproval state. + operationId: approveJob + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Job approved + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/jobs/{id}/reject: + post: + tags: [Jobs] + summary: Reject job + description: Rejects a job in AwaitingApproval state with an optional reason. + operationId: rejectJob + parameters: + - $ref: "#/components/parameters/resourceId" + requestBody: + content: + application/json: + schema: + type: object + properties: + reason: + type: string + responses: + "200": + description: Job rejected + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + + # ─── Policies ──────────────────────────────────────────────────────── + /api/v1/policies: + get: + tags: [Policies] + summary: List policies + operationId: listPolicies + parameters: + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/per_page" + responses: + "200": + description: Paginated list of policies + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/PolicyRule" + "500": + $ref: "#/components/responses/InternalError" + post: + tags: [Policies] + summary: Create policy + operationId: createPolicy + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/PolicyRule" + responses: + "201": + description: Policy created + content: + application/json: + schema: + $ref: "#/components/schemas/PolicyRule" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/policies/{id}: + get: + tags: [Policies] + summary: Get policy + operationId: getPolicy + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Policy details + content: + application/json: + schema: + $ref: "#/components/schemas/PolicyRule" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + put: + tags: [Policies] + summary: Update policy + operationId: updatePolicy + parameters: + - $ref: "#/components/parameters/resourceId" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/PolicyRule" + responses: + "200": + description: Policy updated + content: + application/json: + schema: + $ref: "#/components/schemas/PolicyRule" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + delete: + tags: [Policies] + summary: Delete policy + operationId: deletePolicy + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "204": + description: Policy deleted + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/policies/{id}/violations: + get: + tags: [Policies] + summary: List policy violations + operationId: listPolicyViolations + parameters: + - $ref: "#/components/parameters/resourceId" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/per_page" + responses: + "200": + description: Paginated list of violations + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/PolicyViolation" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + # ─── Profiles ──────────────────────────────────────────────────────── + /api/v1/profiles: + get: + tags: [Profiles] + summary: List profiles + operationId: listProfiles + parameters: + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/per_page" + responses: + "200": + description: Paginated list of profiles + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/CertificateProfile" + "500": + $ref: "#/components/responses/InternalError" + post: + tags: [Profiles] + summary: Create profile + operationId: createProfile + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CertificateProfile" + responses: + "201": + description: Profile created + content: + application/json: + schema: + $ref: "#/components/schemas/CertificateProfile" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/profiles/{id}: + get: + tags: [Profiles] + summary: Get profile + operationId: getProfile + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Profile details + content: + application/json: + schema: + $ref: "#/components/schemas/CertificateProfile" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + put: + tags: [Profiles] + summary: Update profile + operationId: updateProfile + parameters: + - $ref: "#/components/parameters/resourceId" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CertificateProfile" + responses: + "200": + description: Profile updated + content: + application/json: + schema: + $ref: "#/components/schemas/CertificateProfile" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + delete: + tags: [Profiles] + summary: Delete profile + operationId: deleteProfile + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "204": + description: Profile deleted + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + + # ─── Teams ─────────────────────────────────────────────────────────── + /api/v1/teams: + get: + tags: [Teams] + summary: List teams + operationId: listTeams + parameters: + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/per_page" + responses: + "200": + description: Paginated list of teams + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Team" + "500": + $ref: "#/components/responses/InternalError" + post: + tags: [Teams] + summary: Create team + operationId: createTeam + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Team" + responses: + "201": + description: Team created + content: + application/json: + schema: + $ref: "#/components/schemas/Team" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/teams/{id}: + get: + tags: [Teams] + summary: Get team + operationId: getTeam + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Team details + content: + application/json: + schema: + $ref: "#/components/schemas/Team" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + put: + tags: [Teams] + summary: Update team + operationId: updateTeam + parameters: + - $ref: "#/components/parameters/resourceId" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Team" + responses: + "200": + description: Team updated + content: + application/json: + schema: + $ref: "#/components/schemas/Team" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + delete: + tags: [Teams] + summary: Delete team + operationId: deleteTeam + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "204": + description: Team deleted + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + # ─── Owners ────────────────────────────────────────────────────────── + /api/v1/owners: + get: + tags: [Owners] + summary: List owners + operationId: listOwners + parameters: + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/per_page" + responses: + "200": + description: Paginated list of owners + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Owner" + "500": + $ref: "#/components/responses/InternalError" + post: + tags: [Owners] + summary: Create owner + operationId: createOwner + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Owner" + responses: + "201": + description: Owner created + content: + application/json: + schema: + $ref: "#/components/schemas/Owner" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/owners/{id}: + get: + tags: [Owners] + summary: Get owner + operationId: getOwner + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Owner details + content: + application/json: + schema: + $ref: "#/components/schemas/Owner" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + put: + tags: [Owners] + summary: Update owner + operationId: updateOwner + parameters: + - $ref: "#/components/parameters/resourceId" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Owner" + responses: + "200": + description: Owner updated + content: + application/json: + schema: + $ref: "#/components/schemas/Owner" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + delete: + tags: [Owners] + summary: Delete owner + operationId: deleteOwner + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "204": + description: Owner deleted + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + # ─── Agent Groups ─────────────────────────────────────────────────── + /api/v1/agent-groups: + get: + tags: [Agent Groups] + summary: List agent groups + operationId: listAgentGroups + parameters: + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/per_page" + responses: + "200": + description: Paginated list of agent groups + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/AgentGroup" + "500": + $ref: "#/components/responses/InternalError" + post: + tags: [Agent Groups] + summary: Create agent group + operationId: createAgentGroup + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AgentGroup" + responses: + "201": + description: Agent group created + content: + application/json: + schema: + $ref: "#/components/schemas/AgentGroup" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/agent-groups/{id}: + get: + tags: [Agent Groups] + summary: Get agent group + operationId: getAgentGroup + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Agent group details + content: + application/json: + schema: + $ref: "#/components/schemas/AgentGroup" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + put: + tags: [Agent Groups] + summary: Update agent group + operationId: updateAgentGroup + parameters: + - $ref: "#/components/parameters/resourceId" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AgentGroup" + responses: + "200": + description: Agent group updated + content: + application/json: + schema: + $ref: "#/components/schemas/AgentGroup" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + delete: + tags: [Agent Groups] + summary: Delete agent group + operationId: deleteAgentGroup + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "204": + description: Agent group deleted + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/agent-groups/{id}/members: + get: + tags: [Agent Groups] + summary: List agent group members + description: Returns agents matching the group's dynamic criteria plus manually included members. + operationId: listAgentGroupMembers + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: List of member agents + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Agent" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + # ─── Audit ─────────────────────────────────────────────────────────── + /api/v1/audit: + get: + tags: [Audit] + summary: List audit events + operationId: listAuditEvents + parameters: + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/per_page" + responses: + "200": + description: Paginated list of audit events + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/AuditEvent" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/audit/{id}: + get: + tags: [Audit] + summary: Get audit event + operationId: getAuditEvent + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Audit event details + content: + application/json: + schema: + $ref: "#/components/schemas/AuditEvent" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + + # ─── Notifications ────────────────────────────────────────────────── + /api/v1/notifications: + get: + tags: [Notifications] + summary: List notifications + operationId: listNotifications + parameters: + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/per_page" + responses: + "200": + description: Paginated list of notifications + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/PaginationEnvelope" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/NotificationEvent" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/notifications/{id}: + get: + tags: [Notifications] + summary: Get notification + operationId: getNotification + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Notification details + content: + application/json: + schema: + $ref: "#/components/schemas/NotificationEvent" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/notifications/{id}/read: + post: + tags: [Notifications] + summary: Mark notification as read + operationId: markNotificationAsRead + parameters: + - $ref: "#/components/parameters/resourceId" + responses: + "200": + description: Marked as read + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalError" + + # ─── Stats ─────────────────────────────────────────────────────────── + /api/v1/stats/summary: + get: + tags: [Stats] + summary: Dashboard summary + operationId: getDashboardSummary + responses: + "200": + description: High-level system metrics + content: + application/json: + schema: + $ref: "#/components/schemas/DashboardSummary" + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/stats/certificates-by-status: + get: + tags: [Stats] + summary: Certificate status breakdown + operationId: getCertificatesByStatus + responses: + "200": + description: Certificate counts by status + content: + application/json: + schema: + type: object + properties: + status_counts: + type: array + items: + type: object + properties: + status: + type: string + count: + type: integer + format: int64 + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/stats/expiration-timeline: + get: + tags: [Stats] + summary: Expiration timeline + operationId: getExpirationTimeline + parameters: + - name: days + in: query + schema: + type: integer + default: 30 + minimum: 1 + maximum: 365 + responses: + "200": + description: Certificates expiring per day + content: + application/json: + schema: + type: object + properties: + buckets: + type: array + items: + type: object + properties: + date: + type: string + format: date + count: + type: integer + format: int64 + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/stats/job-trends: + get: + tags: [Stats] + summary: Job success/failure trends + operationId: getJobTrends + parameters: + - name: days + in: query + schema: + type: integer + default: 30 + minimum: 1 + maximum: 365 + responses: + "200": + description: Job trends per day + content: + application/json: + schema: + type: object + properties: + trends: + type: array + items: + type: object + properties: + date: + type: string + format: date + completed: + type: integer + format: int64 + failed: + type: integer + format: int64 + "500": + $ref: "#/components/responses/InternalError" + + /api/v1/stats/issuance-rate: + get: + tags: [Stats] + summary: Certificate issuance rate + operationId: getIssuanceRate + parameters: + - name: days + in: query + schema: + type: integer + default: 30 + minimum: 1 + maximum: 365 + responses: + "200": + description: Issuance count per day + content: + application/json: + schema: + type: object + properties: + rate: + type: array + items: + type: object + properties: + date: + type: string + format: date + count: + type: integer + format: int64 + "500": + $ref: "#/components/responses/InternalError" + + # ─── Metrics ───────────────────────────────────────────────────────── + /api/v1/metrics: + get: + tags: [Metrics] + summary: System metrics + description: JSON metrics snapshot with gauges, counters, and uptime. Prometheus format deferred to V3. + operationId: getMetrics + responses: + "200": + description: Metrics snapshot + content: + application/json: + schema: + $ref: "#/components/schemas/MetricsResponse" + "500": + $ref: "#/components/responses/InternalError" + +# ═══════════════════════════════════════════════════════════════════════ +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + description: API key passed as Bearer token. Configure via CERTCTL_AUTH_SECRET. + + parameters: + resourceId: + name: id + in: path + required: true + schema: + type: string + description: Human-readable resource ID (e.g., mc-api-prod, t-platform) + page: + name: page + in: query + schema: + type: integer + default: 1 + minimum: 1 + per_page: + name: per_page + in: query + schema: + type: integer + default: 50 + minimum: 1 + maximum: 500 + + responses: + BadRequest: + description: Validation error + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + NotFound: + description: Resource not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + InternalError: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + schemas: + # ─── Common ────────────────────────────────────────────────────── + ErrorResponse: + type: object + properties: + error: + type: string + request_id: + type: string + + StatusResponse: + type: object + properties: + status: + type: string + + PaginationEnvelope: + type: object + properties: + total: + type: integer + format: int64 + page: + type: integer + per_page: + type: integer + + # ─── Certificates ──────────────────────────────────────────────── + CertificateStatus: + type: string + enum: + - Pending + - Active + - Expiring + - Expired + - RenewalInProgress + - Failed + - Revoked + - Archived + + ManagedCertificate: + type: object + properties: + id: + type: string + name: + type: string + common_name: + type: string + sans: + type: array + items: + type: string + environment: + type: string + owner_id: + type: string + team_id: + type: string + issuer_id: + type: string + target_ids: + type: array + items: + type: string + renewal_policy_id: + type: string + certificate_profile_id: + type: string + status: + $ref: "#/components/schemas/CertificateStatus" + expires_at: + type: string + format: date-time + tags: + type: object + additionalProperties: + type: string + last_renewal_at: + type: string + format: date-time + last_deployment_at: + type: string + format: date-time + revoked_at: + type: string + format: date-time + revocation_reason: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + CertificateVersion: + type: object + properties: + id: + type: string + certificate_id: + type: string + serial_number: + type: string + not_before: + type: string + format: date-time + not_after: + type: string + format: date-time + fingerprint_sha256: + type: string + pem_chain: + type: string + csr_pem: + type: string + key_algorithm: + type: string + key_size: + type: integer + created_at: + type: string + format: date-time + + RevocationReason: + type: string + enum: + - unspecified + - keyCompromise + - caCompromise + - affiliationChanged + - superseded + - cessationOfOperation + - certificateHold + - privilegeWithdrawn + + # ─── Issuers ───────────────────────────────────────────────────── + IssuerType: + type: string + enum: [ACME, GenericCA, StepCA] + + Issuer: + type: object + properties: + id: + type: string + name: + type: string + type: + $ref: "#/components/schemas/IssuerType" + config: + type: object + description: Issuer-specific configuration (varies by type) + enabled: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + # ─── Targets ───────────────────────────────────────────────────── + TargetType: + type: string + enum: [NGINX, Apache, HAProxy, F5, IIS] + + DeploymentTarget: + type: object + properties: + id: + type: string + name: + type: string + type: + $ref: "#/components/schemas/TargetType" + agent_id: + type: string + config: + type: object + description: Target-specific configuration (varies by type) + enabled: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + # ─── Agents ────────────────────────────────────────────────────── + AgentStatus: + type: string + enum: [Online, Offline, Degraded] + + Agent: + type: object + properties: + id: + type: string + name: + type: string + hostname: + type: string + status: + $ref: "#/components/schemas/AgentStatus" + last_heartbeat_at: + type: string + format: date-time + registered_at: + type: string + format: date-time + api_key_hash: + type: string + os: + type: string + architecture: + type: string + ip_address: + type: string + version: + type: string + + WorkItem: + type: object + properties: + id: + type: string + type: + $ref: "#/components/schemas/JobType" + certificate_id: + type: string + common_name: + type: string + sans: + type: array + items: + type: string + target_id: + type: string + target_type: + type: string + target_config: + type: object + status: + $ref: "#/components/schemas/JobStatus" + + # ─── Jobs ──────────────────────────────────────────────────────── + JobType: + type: string + enum: [Issuance, Renewal, Deployment, Validation] + + JobStatus: + type: string + enum: + - Pending + - AwaitingCSR + - AwaitingApproval + - Running + - Completed + - Failed + - Cancelled + + Job: + type: object + properties: + id: + type: string + type: + $ref: "#/components/schemas/JobType" + certificate_id: + type: string + target_id: + type: string + status: + $ref: "#/components/schemas/JobStatus" + attempts: + type: integer + max_attempts: + type: integer + last_error: + type: string + scheduled_at: + type: string + format: date-time + started_at: + type: string + format: date-time + completed_at: + type: string + format: date-time + created_at: + type: string + format: date-time + + # ─── Policies ──────────────────────────────────────────────────── + PolicyType: + type: string + enum: + - AllowedIssuers + - AllowedDomains + - RequiredMetadata + - AllowedEnvironments + - RenewalLeadTime + + PolicySeverity: + type: string + enum: [Warning, Error, Critical] + + PolicyRule: + type: object + properties: + id: + type: string + name: + type: string + type: + $ref: "#/components/schemas/PolicyType" + config: + type: object + description: Policy-specific configuration (varies by type) + enabled: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + PolicyViolation: + type: object + properties: + id: + type: string + certificate_id: + type: string + rule_id: + type: string + message: + type: string + severity: + $ref: "#/components/schemas/PolicySeverity" + created_at: + type: string + format: date-time + + # ─── Profiles ──────────────────────────────────────────────────── + CertificateProfile: + type: object + properties: + id: + type: string + name: + type: string + description: + type: string + allowed_key_algorithms: + type: array + items: + $ref: "#/components/schemas/KeyAlgorithmRule" + max_ttl_seconds: + type: integer + allowed_ekus: + type: array + items: + type: string + required_san_patterns: + type: array + items: + type: string + spiffe_uri_pattern: + type: string + allow_short_lived: + type: boolean + enabled: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + KeyAlgorithmRule: + type: object + properties: + algorithm: + type: string + enum: [RSA, ECDSA, Ed25519] + min_size: + type: integer + + # ─── Teams ─────────────────────────────────────────────────────── + Team: + type: object + properties: + id: + type: string + name: + type: string + description: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + # ─── Owners ────────────────────────────────────────────────────── + Owner: + type: object + properties: + id: + type: string + name: + type: string + email: + type: string + team_id: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + # ─── Agent Groups ──────────────────────────────────────────────── + AgentGroup: + type: object + properties: + id: + type: string + name: + type: string + description: + type: string + match_os: + type: string + match_architecture: + type: string + match_ip_cidr: + type: string + match_version: + type: string + enabled: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + # ─── Audit ─────────────────────────────────────────────────────── + ActorType: + type: string + enum: [User, System, Agent] + + AuditEvent: + type: object + properties: + id: + type: string + actor: + type: string + actor_type: + $ref: "#/components/schemas/ActorType" + action: + type: string + resource_type: + type: string + resource_id: + type: string + details: + type: object + timestamp: + type: string + format: date-time + + # ─── Notifications ─────────────────────────────────────────────── + NotificationType: + type: string + enum: + - ExpirationWarning + - RenewalSuccess + - RenewalFailure + - DeploymentSuccess + - DeploymentFailure + - PolicyViolation + - Revocation + + NotificationChannel: + type: string + enum: [Email, Webhook, Slack] + + NotificationEvent: + type: object + properties: + id: + type: string + type: + $ref: "#/components/schemas/NotificationType" + certificate_id: + type: string + channel: + $ref: "#/components/schemas/NotificationChannel" + recipient: + type: string + message: + type: string + sent_at: + type: string + format: date-time + status: + type: string + error: + type: string + created_at: + type: string + format: date-time + + # ─── Stats & Metrics ───────────────────────────────────────────── + DashboardSummary: + type: object + properties: + total_certificates: + type: integer + format: int64 + expiring_certificates: + type: integer + format: int64 + expired_certificates: + type: integer + format: int64 + revoked_certificates: + type: integer + format: int64 + active_agents: + type: integer + format: int64 + offline_agents: + type: integer + format: int64 + total_agents: + type: integer + format: int64 + pending_jobs: + type: integer + format: int64 + failed_jobs: + type: integer + format: int64 + complete_jobs: + type: integer + format: int64 + completed_at: + type: string + format: date-time + + MetricsResponse: + type: object + properties: + gauge: + type: object + properties: + certificate_total: + type: integer + format: int64 + certificate_active: + type: integer + format: int64 + certificate_expiring_soon: + type: integer + format: int64 + certificate_expired: + type: integer + format: int64 + certificate_revoked: + type: integer + format: int64 + agent_total: + type: integer + format: int64 + agent_online: + type: integer + format: int64 + job_pending: + type: integer + format: int64 + counter: + type: object + properties: + job_completed_total: + type: integer + format: int64 + job_failed_total: + type: integer + format: int64 + uptime: + type: object + properties: + uptime_seconds: + type: integer + format: int64 + server_started: + type: string + format: date-time + measured_at: + type: string + format: date-time