Upload Certificate

POST /v1/certificates/self

Upload a PEM-encoded certificate plus its private key for use on custom-domain endpoints. Once uploaded, attach the cert to one or more endpoints via PUT /v1/endpoints/{uuid}/certificate.

The private key is encrypted at rest with the platform's certificate encryption key (set via SECURITY_CERT_ENCRYPTION_KEY on the api-server). It can never be retrieved through the API — to rotate, delete and re-upload.

Both PEM blocks must parse cleanly. The cert's public key must match the supplied private key (verified server-side). Intermediate CA chains can be appended to the cert_pem as additional -----BEGIN CERTIFICATE----- blocks; the platform serves the full chain in the TLS handshake.

Requires the custom_ssl entitlement on your plan.

Request body

name string Req
Friendly label for the cert. Shown in the dashboard. Max 128 chars.
cert_pem string Req
PEM-encoded leaf certificate, optionally followed by intermediate CA certs (concatenated, in order). Headers -----BEGIN CERTIFICATE----- required.
key_pem string Req
PEM-encoded private key matching the leaf cert. Supports RSA (PKCS#1, PKCS#8) and ECDSA (SEC1, PKCS#8). Encrypted keys (DEK-Info) not supported — decrypt locally before uploading.
domain_uuid string (uuid) Req
The custom domain the certificate is for (its uuid from /domains/available). It must be your active account's own domain; the certificate becomes that domain's default.

Deprecated: domain_id (integer) is still accepted until the sunset. Send domain_uuid; sending both for different objects is a 400 id_uuid_mismatch.

Response (200 OK)

An acknowledgement object. To retrieve the stored certificate object, call GET /v1/certificates/self.

ok boolean
Always true on success.
domain_uuid string (uuid)
The domain the certificate was attached to (and set as default for).
message string
Human-readable confirmation.

Deprecated: until the sunset this response also carries the legacy integer domain_id. Don't use it; read domain_uuid.

Errors

400 Bad Request
Missing fields (no domain: Please select a domain for this certificate.), malformed PEM, cert/key mismatch, expired cert (not_after in the past), unsupported key algorithm, domain_id and domain_uuid naming different domains (id_uuid_mismatch), or an integer domain_id after the sunset (integer_id_unsupported).
401 Unauthorized
Missing auth.
403 Forbidden
Plan doesn't include the custom_ssl entitlement.
404 Not Found
Domain not found. — unknown domain, or another account's (both read the same).
409 Conflict
Cert with the same fingerprint already uploaded.
Request
curl -X POST "https://api.ngris.com/v1/certificates/self" \ -H "X-API-KEY: <your_api_key>" \ -H "Content-Type: application/json" \ -d "$(jq -n \ --arg name "wildcard.example.com" \ --arg cert "$(cat fullchain.pem)" \ --arg key "$(cat privkey.pem)" \ --arg domain "019bd08f-98db-71b2-8e54-f518d319f111" \ '{name: $name, cert_pem: $cert, key_pem: $key, domain_uuid: $domain}')"
Python
from ngris import Ngris client = Ngris(api_key="<your_api_key>") with open("fullchain.pem") as f: cert_pem = f.read() with open("privkey.pem") as f: key_pem = f.read() resp = client.certificates.upload({ "name": "wildcard.example.com", "cert_pem": cert_pem, "key_pem": key_pem, "domain_uuid": "019bd08f-98db-71b2-8e54-f518d319f111", }) print(resp["message"])
Response — 200 OK
{ "ok": true, "domain_uuid": "019bd08f-98db-71b2-8e54-f518d319f111", "message": "Certificate uploaded and set as default for this domain." }
Was this helpful?
On this page
Upload Certificate
Was this helpful?