Skip to content

Certificates

Generated from core/api/router.go. Edit the router, not this file.

MethodPathWho can call it
GET/api/v1/certificatesAny authenticated user
POST/api/v1/certificatesOperator, admin
GET/api/v1/certificates/:idAny authenticated user
PATCH/api/v1/certificates/:idOperator, admin
DELETE/api/v1/certificates/:idAdmin
GET/api/v1/certificates/:id/private-keyAdmin
POST/api/v1/certificates/:id/renewOperator, admin
POST/api/v1/certificates/:id/revokeAdmin

GET /certificates filters on status, environment, common_name, ca_account_id.

Issuing

http
POST /api/v1/certificates
json
{
  "common_name": "app.example.com",
  "sans": ["www.app.example.com"],
  "ca_account_id": "uuid",
  "key_type": "ECDSA",
  "key_size": 256,
  "validity_days": 90,
  "environment": "production",
  "team": "platform",
  "auto_renew": true,
  "renewal_lead_days": 30
}

What happens, in order: the CA account is loaded, policy is evaluated, the gateway is located, its stored configuration is decrypted for exactly this one call, issuance is requested, and the returned certificate is parsed before it is stored. A gateway that returns anything other than a valid X.509 certificate produces a 502 rather than a record marked ISSUED.

Metadata on the stored record — serial, issuer, validity dates, fingerprint, key type and size — is read from the certificate itself, not from what the gateway claimed.

If the gateway returns a private key, it is sealed before it touches the database. Supplying your own CSR avoids this entirely and is the better pattern: the key then stays wherever it was generated.

Returns 201. When non-blocking policy violations were recorded, the body is {"certificate": {...}, "policy_violations": [...]} instead of the bare record — a policy that finds something must say so even when it does not block.

Renewing

POST /certificates/:id/renew runs the same path. The key is rotated and the new one persisted; a renewal that produced a certificate without storing its matching key would leave a record that looks healthy and cannot terminate TLS.

On failure the record is marked RENEWAL_FAILED with renewal_error set, and the previous certificate is left intact.

Exporting a private key

http
GET /api/v1/certificates/:id/private-key
json
{ "common_name": "app.example.com", "private_key_pem": "-----BEGIN..." }

Admin only. Writes cert.private_key_exported to the audit log with the actor and client IP. Returns 404 when no key is stored — which is the normal case for imported, discovered, or CSR-based certificates.

Endpoint detail

GET /api/v1/certificates

Who can call itAny authenticated user
HandlercertHandler.List
Display tokenReadable by an unattended screen

Parameters

NameInDefault
ca_account_idquery
common_namequery
environmentquery
limitquery
offsetquery
statusquery

Responses

StatusBody
200An object with data (Certificate[]), total (integer)
400{ "error": … }
500{ "error": … }
Example request
bash
curl -X GET 'https://certpilot.example.com/api/v1/certificates' \
  -H 'Authorization: Bearer <token>'

POST /api/v1/certificates

Who can call itOperator, admin
HandlercertHandler.Create
Display tokenRefused — not a viewer-safe GET

Request body

RequestCertificateInput defines the payload to request a new certificate.

FieldTypeDescription
common_namestringCommonName is required unless CSRPEM is supplied, in which case the names come from the request itself. Validated below rather than by a binding tag, which cannot express "one of these two".
sansstring[]
ca_account_idstringrequired
csr_pemstringCSRPEM is a certificate signing request whose private key was generated somewhere else and never sent here. This is the path for the keys CertPilot must not hold: an HSM, a load balancer that generates its own, a team whose policy forbids a key leaving their host. The names, key type and key size are taken from the request and the corresponding fields above are ignored, because the only key that can serve the certificate is the one the requester already has — honouring a conflicting key_type here would issue a certificate nobody can use.
key_typestring
key_sizeinteger
validity_daysinteger
environmentstring
teamstring
auto_renewboolean
renewal_lead_daysinteger
metadataobjectMetadata holds values for the admin-defined fields. Required ones are enforced at request time.

Responses

StatusBody
201An object with certificate (Certificate), policy_violations, warnings (string[])
400{ "error": … }
· common_name is required when no csr_pem is supplied
403An object with violations
· Certificate request blocked by security policy
500{ "error": … }
502{ "error": … }
· gateway reported success but returned no certificate
Example request
bash
curl -X POST 'https://certpilot.example.com/api/v1/certificates' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
  "ca_account_id": "<ca_account_id>",
  "common_name": "<common_name>",
  "sans": [
    "<sans>"
  ],
  "csr_pem": "<csr_pem>",
  "key_type": "<key_type>"
}'

GET /api/v1/certificates/:id

Who can call itAny authenticated user
HandlercertHandler.Get
Display tokenReadable by an unattended screen

Parameters

NameInDefault
idpathrequired

Responses

StatusBody
200A Certificate object
404{ "error": … }
Example request
bash
curl -X GET 'https://certpilot.example.com/api/v1/certificates/<id>' \
  -H 'Authorization: Bearer <token>'

PATCH /api/v1/certificates/:id

Who can call itOperator, admin
HandlercertHandler.UpdateMetadata
Display tokenRefused — not a viewer-safe GET

The only mutable part of a certificate: the decisions people record about it, not the facts the CA established.

Parameters

NameInDefault
idpathrequired

Request body

UpdateMetadataInput is what an operator may change on a certificate. Pointers, so an omitted field is left alone rather than cleared. A form that edits only the team must not blank the environment, and a client that knows about three of these fields must not erase the fourth.

FieldTypeDescription
environmentstring
teamstring
tagsstring[]
metadataobject

Responses

StatusBody
200A Certificate object
400{ "error": … }
404{ "error": … }
500{ "error": … }
Example request
bash
curl -X PATCH 'https://certpilot.example.com/api/v1/certificates/<id>' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
  "environment": "<environment>",
  "team": "<team>",
  "tags": [
    "<tags>"
  ],
  "metadata": {}
}'

DELETE /api/v1/certificates/:id

Who can call itAdmin
HandlercertHandler.Delete
Display tokenRefused — not a viewer-safe GET

Parameters

NameInDefault
idpathrequired

Responses

StatusBody
200An object with message (string)
404{ "error": … }
· no such certificate
409An object with not_after, status
500{ "error": … }
Example request
bash
curl -X DELETE 'https://certpilot.example.com/api/v1/certificates/<id>' \
  -H 'Authorization: Bearer <token>'

GET /api/v1/certificates/:id/private-key

Who can call itAdmin
HandlercertHandler.PrivateKey
Display tokenRefused — not a viewer-safe GET

Parameters

NameInDefault
idpathrequired

Responses

StatusBody
200An object with common_name, private_key_pem
404{ "error": … }
· no private key is stored for this certificate — it was either imported, discovered, or issued from a CSR whose key never left its host
500{ "error": … }
Example request
bash
curl -X GET 'https://certpilot.example.com/api/v1/certificates/<id>/private-key' \
  -H 'Authorization: Bearer <token>'

POST /api/v1/certificates/:id/renew

Who can call itOperator, admin
HandlercertHandler.Renew
Display tokenRefused — not a viewer-safe GET

Parameters

NameInDefault
idpathrequired

Responses

StatusBody
202An object with data (RenewalJob), message (string)
400{ "error": … }
404{ "error": … }
500{ "error": … }
Example request
bash
curl -X POST 'https://certpilot.example.com/api/v1/certificates/<id>/renew' \
  -H 'Authorization: Bearer <token>'

POST /api/v1/certificates/:id/revoke

Who can call itAdmin
HandlercertHandler.Revoke
Display tokenRefused — not a viewer-safe GET

Exporting a private key is admin-only and audited: it is the one operation that removes a secret from the system's custody. Revocation is admin, and it is the operation DELETE was being used for. It tells the CA first and records only what the CA accepted, so a certificate can never read REVOKED here while still answering handshakes in production.

Parameters

NameInDefault
idpathrequired

Request body

RevokeCertificateInput names the reason. It is required rather than defaulted: "unspecified" is a legitimate answer, but it should be one somebody chose, because the reason is what tells the next reader whether a key was compromised or a service was simply retired.

FieldTypeDescription
reasonintegerrequired

Responses

StatusBody
200An object with certificate (Certificate), message
400{ "error": … }
404{ "error": … }
· no such certificate
409An object with revoked_at
· this certificate is already revoked
500{ "error": … }
502{ "error": … }
· the CA account this certificate was issued by could not be read
Example request
bash
curl -X POST 'https://certpilot.example.com/api/v1/certificates/<id>/revoke' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
  "reason": 0
}'