Download a certificate
For operators who need to export a cert manually — emergency deployments, debugging, audit snapshots. A direct download returns the material straight from the API; an approval-gated download is redeemed once through a single-use token with a short expiry. Every download emits a cert.downloaded event with the actor and the selected format.
Supported formats
The format field accepts ten values. The response is JSON: the public PEM fields (cert_pem, chain_pem, fullchain_pem) are always present, and the binary formats add base64 fields on top. The UI turns the response into the file(s) below.
private_key is returned only when the requested format actually needs it — pem (the default when format is omitted), key, bundle and all. It is not returned for cert, chain and fullchain (public material only), nor for pfx, jks and der, whose containers already carry the key in their own encoding. A download therefore never hands out key material the caller did not ask for.
| Format | Contents | Typical use |
|---|---|---|
pem | The PEM fields only. The API default when format is omitted. | Scripted access that parses the JSON itself. |
cert | Leaf certificate (.crt) | Nginx ssl_certificate when the chain is separate. |
chain | Intermediate(s) only (.pem) | Servers that want cert + chain in separate files. |
fullchain | Leaf + intermediates (.pem) | Most nginx / Apache configs (ssl_certificate). |
key | Private key (PKCS#8 PEM, .key) | Web server config. |
bundle | Fullchain + private key in one .pem | HAProxy and anything else that wants a single file. |
pfx | PKCS#12 — leaf + key + chain, password-protected (.pfx) | Windows / IIS import. A password may be supplied; when omitted, one is generated and returned. |
der | Binary DER certificate (.der) plus a PKCS#8 DER key (.key.der) — two files | Appliances and Java tooling that reject PEM. |
jks | Java KeyStore (.jks), password + entry alias | Java application servers. Password and alias may be supplied; when omitted, a password is generated and the alias defaults to the primary domain. |
all | A zip of cert, chain, fullchain, and key | One-click snapshot for manual handoff. |
password is accepted only for pfx and jks; alias only for jks. Any other combination is rejected with 400.
Steps
- Open the certificate detail page.
- Click Download (top-right actions) and pick a format from the menu.
- For PFX and JKS a small modal collects an optional password (and, for JKS, an entry alias). Leave them empty to have CertAutoPilot generate a password — it is shown once after the download.
- The file downloads straight from the API response.
derproduces two files;allproduces a zip.
When the certificate policy requires approval for downloads, the direct call returns an approval request instead of the material. Executing the approved request (POST /approval-requests/{id}/execute) returns a download_token in its response body; that token is redeemed once at GET /downloads/{token} and is invalidated on use. It is valid for 60 seconds, so a leaked URL stops working almost immediately — a re-download needs a fresh approval execution.
Version history & downloading a past version
Every renewal keeps the previous certificate and its private key as a historical version — nothing is overwritten. The certificate detail page's Certificates tab lists every version with its serial number, expiry, and issue date; each row has its own Download button, so you can export any past version (cert, chain, key, or a zip) — the matching key for that version is served automatically.
Retention is bounded by History Versions in the Certificate Defaults card of Settings → General (API field cert_history_max_versions; default 10 versions per certificate, -1 = keep everything). Pruning runs at every artifact write — issuance, renewal, and certificate import alike, not only at renewal. When a new version pushes the count over the limit, the oldest versions — and any private key no longer referenced by a retained version — are pruned automatically. A private key shared across several retained versions is never dropped while any of them still needs it.
Pruning also pins any version a distribution still references (deployed, rolled back to, or awaiting a queued rollback): a pinned version is never pruned, even when it falls past the retention count. See retention pinning.
Via API, add artifact_id (from artifact_history[].id on the certificate detail response) to the download body to fetch a specific version:
curl -X POST -H "Authorization: Bearer <access_token>" \
https://cap.example.com/api/v1/projects/$PID/certificates/$CID/download \
-d '{"format":"fullchain","artifact_id":"<version-id>"}'
When Require approval for download is on, an approved download always returns the current version. Per-version download is available on the direct path — for admin/owner users, or when the download-approval requirement is off.
Permissions
- Operator role or higher is required to download.
- There is no separate key-export permission. Any role that can download can download the private key, in every format that carries one (
key.pem, PKCS#12, bundle). If you need to restrict this, restrict the role. - When Require approval for downloads is enabled in the general settings, a
downloadapproval request is created instead — see Approval workflow.
Audit trail
Every download writes:
- An
audit_logsentry: actor (user), format, IP address, timestamp. - A
cert.downloadedevent on the certificate timeline. - For an approval-gated download, a record in the
download_tokenscollection. It is not a long-lived forensic record: a MongoDB TTL index onexpires_atreaps each row about a minute after it expires. Use the audit log and the certificate timeline for retrospective analysis.
API access
There is one download endpoint, and it returns the payload directly — nothing is minted first:
curl -X POST -H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
https://cap.example.com/api/v1/projects/$PID/certificates/$CID/download \
-d '{"format":"fullchain"}'
# → {"cert_pem":"...","chain_pem":"...","fullchain_pem":"...","private_key":"..."}
Binary formats add base64 fields and, where applicable, the generated password:
curl -X POST -H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
https://cap.example.com/api/v1/projects/$PID/certificates/$CID/download \
-d '{"format":"pfx","password":""}'
# → {..., "pfx_base64":"MIIK...", "pfx_password":"a3f9..."}
The separate token route exists only for approval-gated downloads. When approval is required, the call above returns an approval request; after the request is approved, executing it hands back a single-use token:
# Execute the approved request — the response carries the token
curl -X POST -H "Authorization: Bearer <access_token>" \
https://cap.example.com/api/v1/approval-requests/$RID/execute
# → {"download_token":"...", ...}
# Redeem it once, within 60 seconds
curl -H "Authorization: Bearer <access_token>" \
https://cap.example.com/api/v1/downloads/$TOKEN
The token route always serves the current version in the default PEM shape — it takes no format or artifact_id.
See API reference for full schema.
Troubleshooting
403 on a download
The role is below Operator on that project. There is no key-export policy toggle to check — if downloads are gated, it is the Require approval for downloads setting, which returns a pending approval request rather than a 403.
"token not found or expired"
Approval download tokens live 60 seconds and are single-use. Re-execute the approved request to get a fresh one; if the approval request itself has already been consumed, submit a new download request.
PKCS#12 bundle won't import
The pfx format is always password-protected — use the password returned in pfx_password (shown once in the UI after the download). There is no legacy-encryption toggle on the download path. If a very old importer rejects the bundle, export cert + key (or der) instead and build the keystore locally with openssl pkcs12 -export -legacy.