Files
certctl/api/openapi.yaml
T
Shankar 078ba5ab7b feat: add Vault PKI and DigiCert CertCentral issuer connectors (M32 + M37)
Vault PKI: synchronous issuance via /v1/{mount}/sign/{role}, token auth,
revocation, CA cert retrieval, 14 tests. DigiCert CertCentral: async order
model (submit → poll → download), X-DC-DEVKEY auth, OV/EV support, PEM
bundle parsing, 16 tests. Both conditionally registered based on env vars.
Includes OpenAPI enum updates, seed data, connector docs, architecture docs,
README badges, and testing guide sign-off (Parts 38 + 39, 12 automated
smoke test assertions all passing).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 17:19:46 -04:00

3345 lines
98 KiB
YAML

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
- name: Discovery
description: Certificate discovery — filesystem scanning by agents and network TLS probing
- name: Network Scan
description: Network scan target management for active TLS certificate discovery
- name: Digest
description: Scheduled certificate digest email notifications
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"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
delete:
tags: [Certificates]
summary: Archive certificate
operationId: archiveCertificate
parameters:
- $ref: "#/components/parameters/resourceId"
responses:
"204":
description: Certificate archived
"404":
$ref: "#/components/responses/NotFound"
"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"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
"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"
# ─── Certificate Export ──────────────────────────────────────────────
/api/v1/certificates/{id}/export/pem:
get:
tags: [Certificates]
summary: Export certificate as PEM
description: |
Returns the certificate and its chain in PEM format. By default returns JSON
with cert_pem, chain_pem, and full_pem fields. Add ?download=true to get the
full PEM chain as a file download with Content-Disposition headers.
operationId: exportCertificatePEM
parameters:
- $ref: "#/components/parameters/resourceId"
- name: download
in: query
schema:
type: string
enum: ["true"]
description: Set to "true" to get a file download instead of JSON.
responses:
"200":
description: PEM export
content:
application/json:
schema:
type: object
properties:
cert_pem:
type: string
description: Leaf certificate PEM
chain_pem:
type: string
description: Intermediate/root chain PEM
full_pem:
type: string
description: Full PEM chain (cert + intermediates)
application/x-pem-file:
schema:
type: string
format: binary
description: Full PEM file (when download=true)
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/certificates/{id}/export/pkcs12:
post:
tags: [Certificates]
summary: Export certificate as PKCS#12
description: |
Returns a PKCS#12 (.p12) bundle containing the certificate and chain.
Private keys are NOT included — they live on agents and never touch the control plane.
The bundle is encrypted with the provided password (or empty password if omitted).
operationId: exportCertificatePKCS12
parameters:
- $ref: "#/components/parameters/resourceId"
requestBody:
content:
application/json:
schema:
type: object
properties:
password:
type: string
description: Password to encrypt the PKCS#12 bundle (can be empty)
responses:
"200":
description: PKCS#12 binary
content:
application/x-pkcs12:
schema:
type: string
format: binary
"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"
"409":
$ref: "#/components/responses/Conflict"
"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"
"404":
$ref: "#/components/responses/NotFound"
"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. See also /api/v1/metrics/prometheus for Prometheus exposition format.
operationId: getMetrics
responses:
"200":
description: Metrics snapshot
content:
application/json:
schema:
$ref: "#/components/schemas/MetricsResponse"
"500":
$ref: "#/components/responses/InternalError"
# ─── Prometheus Metrics (M22) ──────────────────────────────────────
/api/v1/metrics/prometheus:
get:
tags: [Metrics]
summary: Prometheus metrics
description: |
Prometheus exposition format metrics. Compatible with Prometheus, Grafana Agent,
Datadog Agent, Victoria Metrics, and any OpenMetrics scraper.
Returns 11 metrics with certctl_ prefix (8 gauges, 2 counters, 1 info).
operationId: getPrometheusMetrics
responses:
"200":
description: Prometheus text format
content:
text/plain:
schema:
type: string
description: "Prometheus exposition format (text/plain; version=0.0.4)"
"500":
$ref: "#/components/responses/InternalError"
# ─── Certificate Deployments (M20) ─────────────────────────────────
/api/v1/certificates/{id}/deployments:
get:
tags: [Certificates]
summary: List certificate deployments
description: Returns deployment targets associated with this certificate.
operationId: getCertificateDeployments
parameters:
- $ref: "#/components/parameters/resourceId"
responses:
"200":
description: Deployment targets for this certificate
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/DeploymentTarget"
total:
type: integer
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
# ─── Discovery (M18b) ─────────────────────────────────────────────
/api/v1/agents/{id}/discoveries:
post:
tags: [Discovery]
summary: Submit discovery report
description: |
Agent submits a batch of discovered certificates from filesystem scanning.
Server deduplicates by (fingerprint, agent_id, source_path) and records scan metadata.
operationId: submitDiscoveryReport
parameters:
- $ref: "#/components/parameters/resourceId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/DiscoveryReport"
responses:
"202":
description: Report accepted and processed
content:
application/json:
schema:
$ref: "#/components/schemas/DiscoveryScan"
"400":
$ref: "#/components/responses/BadRequest"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/discovered-certificates:
get:
tags: [Discovery]
summary: List discovered certificates
description: Returns discovered certificates with optional filters by agent and triage status.
operationId: listDiscoveredCertificates
parameters:
- $ref: "#/components/parameters/page"
- $ref: "#/components/parameters/per_page"
- name: agent_id
in: query
schema:
type: string
description: Filter by discovering agent
- name: status
in: query
schema:
type: string
enum: [Unmanaged, Managed, Dismissed]
description: Filter by triage status
responses:
"200":
description: Paginated list of discovered certificates
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/PaginationEnvelope"
- type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/DiscoveredCertificate"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/discovered-certificates/{id}:
get:
tags: [Discovery]
summary: Get discovered certificate
description: Returns a single discovered certificate by ID.
operationId: getDiscoveredCertificate
parameters:
- $ref: "#/components/parameters/resourceId"
responses:
"200":
description: Discovered certificate details
content:
application/json:
schema:
$ref: "#/components/schemas/DiscoveredCertificate"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/discovered-certificates/{id}/claim:
post:
tags: [Discovery]
summary: Claim discovered certificate
description: Links a discovered certificate to an existing managed certificate. Changes status to Managed.
operationId: claimDiscoveredCertificate
parameters:
- $ref: "#/components/parameters/resourceId"
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [managed_certificate_id]
properties:
managed_certificate_id:
type: string
description: ID of the managed certificate to link to
responses:
"200":
description: Certificate claimed
content:
application/json:
schema:
$ref: "#/components/schemas/StatusMessageResponse"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/discovered-certificates/{id}/dismiss:
post:
tags: [Discovery]
summary: Dismiss discovered certificate
description: Marks a discovered certificate as dismissed (excluded from triage queue).
operationId: dismissDiscoveredCertificate
parameters:
- $ref: "#/components/parameters/resourceId"
responses:
"200":
description: Certificate dismissed
content:
application/json:
schema:
$ref: "#/components/schemas/StatusMessageResponse"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/discovery-scans:
get:
tags: [Discovery]
summary: List discovery scans
description: Returns history of discovery scan executions with optional agent filter.
operationId: listDiscoveryScans
parameters:
- $ref: "#/components/parameters/page"
- $ref: "#/components/parameters/per_page"
- name: agent_id
in: query
schema:
type: string
description: Filter by agent ID
responses:
"200":
description: Paginated list of discovery scans
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/PaginationEnvelope"
- type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/DiscoveryScan"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/discovery-summary:
get:
tags: [Discovery]
summary: Discovery status summary
description: Returns aggregate counts of discovered certificates by triage status.
operationId: getDiscoverySummary
responses:
"200":
description: Status counts
content:
application/json:
schema:
type: object
properties:
Unmanaged:
type: integer
Managed:
type: integer
Dismissed:
type: integer
"500":
$ref: "#/components/responses/InternalError"
# ─── Network Scan Targets (M21) ───────────────────────────────────
/api/v1/network-scan-targets:
get:
tags: [Network Scan]
summary: List network scan targets
description: Returns all configured network scan targets with CIDR ranges and ports.
operationId: listNetworkScanTargets
responses:
"200":
description: List of network scan targets
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/PaginationEnvelope"
- type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/NetworkScanTarget"
"500":
$ref: "#/components/responses/InternalError"
post:
tags: [Network Scan]
summary: Create network scan target
description: |
Creates a new network scan target. CIDR ranges are validated and capped at /20
(4096 IPs max per CIDR) to prevent accidental huge scans.
operationId: createNetworkScanTarget
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/NetworkScanTargetCreate"
responses:
"201":
description: Target created
content:
application/json:
schema:
$ref: "#/components/schemas/NetworkScanTarget"
"400":
$ref: "#/components/responses/BadRequest"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/network-scan-targets/{id}:
get:
tags: [Network Scan]
summary: Get network scan target
description: Returns a single network scan target by ID.
operationId: getNetworkScanTarget
parameters:
- $ref: "#/components/parameters/resourceId"
responses:
"200":
description: Network scan target details
content:
application/json:
schema:
$ref: "#/components/schemas/NetworkScanTarget"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
put:
tags: [Network Scan]
summary: Update network scan target
description: Updates an existing network scan target.
operationId: updateNetworkScanTarget
parameters:
- $ref: "#/components/parameters/resourceId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/NetworkScanTargetCreate"
responses:
"200":
description: Target updated
content:
application/json:
schema:
$ref: "#/components/schemas/NetworkScanTarget"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
delete:
tags: [Network Scan]
summary: Delete network scan target
description: Deletes a network scan target.
operationId: deleteNetworkScanTarget
parameters:
- $ref: "#/components/parameters/resourceId"
responses:
"204":
description: Target deleted
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/network-scan-targets/{id}/scan:
post:
tags: [Network Scan]
summary: Trigger network scan
description: |
Triggers an immediate scan of the specified target. Scans all configured CIDRs and ports
concurrently (50 goroutines). Results feed into the discovery pipeline for deduplication.
operationId: triggerNetworkScan
parameters:
- $ref: "#/components/parameters/resourceId"
responses:
"202":
description: Scan completed with certificates found
content:
application/json:
schema:
$ref: "#/components/schemas/DiscoveryScan"
"200":
description: Scan completed, no certificates found
content:
application/json:
schema:
$ref: "#/components/schemas/StatusMessageResponse"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
# ─── Digest ────────────────────────────────────────────────────────
/api/v1/digest/preview:
get:
tags: [Digest]
summary: Preview digest email
description: |
Returns an HTML preview of the scheduled certificate digest email.
This includes a summary of certificate status, pending jobs, and expiring certificates.
operationId: previewDigest
responses:
"200":
description: HTML digest email preview
content:
text/html:
schema:
type: string
example: "<html>...</html>"
"503":
description: Digest service not configured
content:
application/json:
schema:
$ref: "#/components/schemas/StatusMessageResponse"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/digest/send:
post:
tags: [Digest]
summary: Send digest email
description: |
Triggers immediate sending of the certificate digest email to configured recipients.
If no explicit recipients are configured, sends to certificate owners.
operationId: sendDigest
responses:
"200":
description: Digest sent successfully
content:
application/json:
schema:
$ref: "#/components/schemas/StatusMessageResponse"
"503":
description: Digest service not configured
content:
application/json:
schema:
$ref: "#/components/schemas/StatusMessageResponse"
"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"
Conflict:
description: Resource conflict
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
required:
- name
- common_name
- renewal_policy_id
- issuer_id
- owner_id
- team_id
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, VaultPKI, DigiCert]
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
description: Extended Key Usages to include in issued certificates
items:
type: string
enum:
- serverAuth
- clientAuth
- codeSigning
- emailProtection
- timeStamping
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
# ─── Discovery (M18b) ────────────────────────────────────────────
DiscoveredCertificate:
type: object
properties:
id:
type: string
fingerprint_sha256:
type: string
common_name:
type: string
sans:
type: array
items:
type: string
serial_number:
type: string
issuer_dn:
type: string
subject_dn:
type: string
not_before:
type: string
format: date-time
nullable: true
not_after:
type: string
format: date-time
nullable: true
key_algorithm:
type: string
key_size:
type: integer
is_ca:
type: boolean
source_path:
type: string
source_format:
type: string
agent_id:
type: string
discovery_scan_id:
type: string
nullable: true
managed_certificate_id:
type: string
nullable: true
status:
type: string
enum: [Unmanaged, Managed, Dismissed]
first_seen_at:
type: string
format: date-time
last_seen_at:
type: string
format: date-time
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
DiscoveryScan:
type: object
properties:
id:
type: string
agent_id:
type: string
directories:
type: array
items:
type: string
certificates_found:
type: integer
certificates_new:
type: integer
errors_count:
type: integer
scan_duration_ms:
type: integer
started_at:
type: string
format: date-time
completed_at:
type: string
format: date-time
nullable: true
DiscoveryReport:
type: object
required: [agent_id, directories, certificates]
properties:
agent_id:
type: string
directories:
type: array
items:
type: string
certificates:
type: array
items:
type: object
properties:
fingerprint_sha256:
type: string
common_name:
type: string
sans:
type: array
items:
type: string
serial_number:
type: string
issuer_dn:
type: string
subject_dn:
type: string
not_before:
type: string
not_after:
type: string
key_algorithm:
type: string
key_size:
type: integer
is_ca:
type: boolean
pem_data:
type: string
source_path:
type: string
source_format:
type: string
errors:
type: array
items:
type: string
scan_duration_ms:
type: integer
StatusMessageResponse:
type: object
properties:
status:
type: string
message:
type: string
# ─── Network Scan (M21) ──────────────────────────────────────────
NetworkScanTarget:
type: object
properties:
id:
type: string
name:
type: string
cidrs:
type: array
items:
type: string
description: CIDR ranges to scan (max /20 per CIDR)
ports:
type: array
items:
type: integer
description: TCP ports to probe for TLS
enabled:
type: boolean
scan_interval_hours:
type: integer
description: Hours between scheduled scans
timeout_ms:
type: integer
description: Per-connection timeout in milliseconds
last_scan_at:
type: string
format: date-time
nullable: true
last_scan_duration_ms:
type: integer
nullable: true
last_scan_certs_found:
type: integer
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
NetworkScanTargetCreate:
type: object
required: [name, cidrs]
properties:
name:
type: string
cidrs:
type: array
items:
type: string
description: CIDR ranges (max /20 per CIDR, max 4096 IPs)
ports:
type: array
items:
type: integer
description: TCP ports to probe (default [443])
enabled:
type: boolean
default: true
scan_interval_hours:
type: integer
default: 6
timeout_ms:
type: integer
default: 5000