Upload Client CA

POST /v1/client-cas

Add a trusted root CA used to validate incoming client certificates on mTLS-enabled endpoints. Once uploaded, attach the CA to one or more endpoints via PUT /v1/endpoints/{uuid}/mtls.

The platform parses the PEM, verifies it's actually a CA cert (basicConstraints CA:TRUE), extracts subject/issuer/fingerprint metadata, and stores the original PEM bytes for chain validation at TLS-handshake time. Exactly one certificate per upload — bundles (more than one CERTIFICATE block) are rejected; split them and upload each CA separately.

An account holds each CA once (one per fingerprint), whichever member uploaded it. Uploading a CA the account already has creates nothing and uses none of your plan's client CA limit: it answers 200 OK with that CA and "existing": true, so the call is safe to repeat. Plan-gated: requires the mtls_attachment entitlement on your plan.

Request body

name string Req
Friendly label. Required, max 128 chars. Shown in the dashboard's mTLS config dropdown.
description string
Optional longer description. Max 512 chars.
ca_pem string Req
PEM-encoded CA certificate. Must include -----BEGIN CERTIFICATE----- headers and parse as a valid X.509 cert with basicConstraints: CA:TRUE.

Response (201 Created; 200 OK when the account already has the CA)

uuid string (uuid)
The CA's uuid — the new one, or the one the account already had. Pass it to the mTLS config in ca_uuids.
existing boolean
false with 201: the CA was added. true with 200: the account already had this CA (same fingerprint); nothing was added, and name is the existing CA's.
name string
The CA's label.
fingerprint string
SHA-256 fingerprint of the CA cert. Used for deduplication.
subject_cn string
Subject Common Name extracted from the cert.
not_after datetime
CA expiry — useful to verify before saving.
quota_paused_at datetime
Only on a 200, while the existing CA is paused because the account is over its plan's client CA limit: the CA is kept, but endpoints don't trust it until the account has room again (it resumes on its own). Absent otherwise.

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

Errors

400 Bad Request
Missing name, name > 128 chars, description > 512 chars, malformed PEM, more than one certificate in the PEM (bundles rejected), cert isn't a CA, the CA has expired or isn't valid yet, or it isn't allowed to sign certificates. The message says what is wrong; upload a current CA certificate.
401 Unauthorized
Missing auth.
403 Forbidden
Caller lacks the cert:write permission, or the plan doesn't include the mtls_attachment entitlement.
Request
curl -X POST "https://api.ngris.com/v1/client-cas" \ -H "X-API-KEY: <your_api_key>" \ -H "Content-Type: application/json" \ -d "$(jq -n \ --arg name "internal-corp-ca" \ --arg pem "$(cat root-ca.pem)" \ '{name: $name, ca_pem: $pem}')"
Python
from ngris import Ngris client = Ngris(api_key="<your_api_key>") with open("root-ca.pem") as f: pem = f.read() ca = client.client_cas.create({ "name": "internal-corp-ca", "description": "Acme Corp internal PKI root", "ca_pem": pem, }) print(f"CA {ca['uuid']}, fingerprint {ca['fingerprint']}")
Response — 201 Created
{ "uuid": "019af8a2-4b16-70ea-8090-9f63343250c8", "name": "internal-corp-ca", "fingerprint": "ab:cd:ef:01:23:...", "subject_cn": "Acme Corp Root CA", "not_after": "2034-01-01T00:00:00Z", "existing": false }
Response — 200 OK (the account already has this CA)
{ "uuid": "019af8a2-4b16-70ea-8090-9f63343250c8", "name": "internal-corp-ca", "fingerprint": "ab:cd:ef:01:23:...", "subject_cn": "Acme Corp Root CA", "not_after": "2034-01-01T00:00:00Z", "existing": true }
Was this helpful?
On this page
Upload Client CA
Was this helpful?