# Certificates API

3 operations under /api/v1. Errors are RFC 9457 problem+json with a stable `code` field. The complete schemas are in https://nebulactrl.com/api/v1/openapi.json.

## DELETE /certificates/{certificateId}

Delete a certificate (staged removal when it is installed)

Refused with 409 `certificate_in_use` while a domain pins it.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `certificateId` (path, string, required)

Response 204: No Content
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict (`certificate_in_use`); 422 Unprocessable Entity; 500 Internal Server Error

## GET /servers/{serverId}/certificates

List a server's custom certificates

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `serverId` (path, string, required)

Response 200: OK (CertificatesOutputBody)
  - `items` (ServerCertificate[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## POST /servers/{serverId}/certificates

Upload a custom TLS certificate (staged until the proxy is deployed)

Needs `proxy:write`. The leaf is the first certificate of the chain; encrypted keys, mismatching keys, expired and not-yet-valid certificates and certificates without a SAN are rejected. The key is sealed at rest and never returned.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `Idempotency-Key` (header, string): Client-chosen key (<=255 chars). A retry with the same key replays the original response for 24 hours.
- `serverId` (path, string, required)

Request body (application/json, required): UploadCertificateInputBody
  - `certPem` (string, required): ServerCertificate chain in PEM, leaf first
  - `keyPem` (string, required): Unencrypted private key in PEM. Write-only: sealed at rest, never returned.
  - `name` (string, required): Unique per server

Response 201: Created (ServerCertificate)
  - `appliedAt` (string (date-time) | null, required)
  - `change` (enum: none | add | remove, required): Staged change awaiting Deploy proxy
  - `createdAt` (string (date-time), required)
  - `daysToExpiry` (integer (int64), required): Whole days until NotAfter (negative when expired)
  - `domains` (string[] | null, required): Subject Alternative Names (DNS and IP)
  - `fingerprint` (string, required): SHA-256 of the leaf certificate, hex
  - `id` (string, required)
  - `issuer` (string, required)
  - `name` (string, required)
  - `notAfter` (string (date-time), required)
  - `notBefore` (string (date-time), required)
  - `serverId` (string, required)
  - `status` (enum: pending | active | error, required): pending: never applied; active: loaded by the proxy; error: expired or the proxy could not load it
  - `statusMessage` (string, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error
