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: "..." "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, Sectigo, GoogleCAS] 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, Traefik, Caddy, Envoy, Postfix, Dovecot, IIS, F5] 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