---
title: PKI Certificates
path: api-guides/pki-certificates
status: published
---

# PKI Certificates

Run an internal CA, issue certificates, manage CSRs and trust anchors. For ACME and public cert issuance, see [ACME](./acme). For the conceptual model, see [PKI](../core-concepts/pki).

**Base path:** `/v1/pki/`

## Create an internal CA

Start with a root:

```bash
curl -X POST https://scaivault.scailabs.ai/v1/pki/ca \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "acme-root",
    "common_name": "Acme Root CA",
    "ca_type": "root",
    "key_type": "ec",
    "key_size": 256,
    "validity_days": 3650
  }'
```

```python
resp = httpx.post(
    "https://scaivault.scailabs.ai/v1/pki/ca",
    headers={"Authorization": f"Bearer {os.environ['SCAIVAULT_TOKEN']}"},
    json={
        "name": "acme-root",
        "common_name": "Acme Root CA",
        "ca_type": "root",
        "key_type": "ec",
        "key_size": 256,
        "validity_days": 3650,
    },
)
```

```typescript
const resp = await fetch("https://scaivault.scailabs.ai/v1/pki/ca", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SCAIVAULT_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "acme-root",
    common_name: "Acme Root CA",
    ca_type: "root",
    key_type: "ec",
    key_size: 256,
    validity_days: 3650,
  }),
});
```

Response:

```json
{
  "id": "ca_root_abc",
  "name": "acme-root",
  "common_name": "Acme Root CA",
  "ca_type": "root",
  "key_type": "ec",
  "key_size": 256,
  "valid_from": "2026-04-23T14:00:00Z",
  "valid_until": "2036-04-20T14:00:00Z",
  "is_active": true,
  "certificates_issued": 0,
  "created_at": "2026-04-23T14:00:00Z"
}
```

Then an intermediate:

```bash
curl -X POST https://scaivault.scailabs.ai/v1/pki/ca \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "acme-mtls-intermediate",
    "common_name": "Acme mTLS Intermediate",
    "ca_type": "intermediate",
    "parent_ca_id": "ca_root_abc",
    "key_type": "ec",
    "key_size": 256,
    "validity_days": 1825
  }'
```

### Fields

| Field | Required | Description |
|-------|----------|-------------|
| `name` | Yes | Tenant-unique |
| `common_name` | Yes | Subject CN |
| `ca_type` | Yes | `root` or `intermediate` |
| `parent_ca_id` | For intermediates | Parent CA ID |
| `key_type` | No | `rsa` (default) or `ec` |
| `key_size` | No | `2048`/`4096` for RSA; `256`/`384` for EC |
| `validity_days` | No | Default `3650` for roots, `1825` for intermediates |

## Get CA certificate in PEM

```bash
curl -H "Authorization: Bearer $TOKEN" \
     https://scaivault.scailabs.ai/v1/pki/ca/ca_root_abc/certificate
```

Response:

```json
{
  "certificate_pem": "-----BEGIN CERTIFICATE-----\n..."
}
```

Distribute this to your clients as a trust root.

## Create a PKI role

A role constrains issuance: what domains, what key types, what TTL.

```bash
curl -X POST https://scaivault.scailabs.ai/v1/pki/roles \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "svc-mtls",
    "ca_id": "ca_intermediate_mtls",
    "allowed_domains": ["*.svc.cluster.local", "*.internal"],
    "allow_subdomains": true,
    "allow_ip_sans": true,
    "max_ttl": "720h",
    "key_type": "ec",
    "key_bits": 256,
    "require_cn": true
  }'
```

### Role fields

| Field | Description |
|-------|-------------|
| `ca_id` | Which CA signs certs for this role |
| `allowed_domains` | Allow-list of domain patterns |
| `allow_subdomains` | Whether `*.allowed-domain.com` is OK |
| `allow_ip_sans` | Allow IP SANs |
| `max_ttl` | Cap on requested TTL |
| `key_type`, `key_bits` | Required key type for issued certs |
| `require_cn` | Reject CSRs without a CN |

## Issue a certificate

```bash
curl -X POST https://scaivault.scailabs.ai/v1/pki/issue/svc-mtls \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "common_name": "billing.svc.cluster.local",
    "alt_names": ["billing-api.svc.cluster.local"],
    "ip_sans": ["10.0.5.100"],
    "ttl": "168h"
  }'
```

Response (`201 Created`):

```json
{
  "certificate": "-----BEGIN CERTIFICATE-----\n...",
  "private_key": "-----BEGIN EC PRIVATE KEY-----\n...",
  "ca_chain": ["-----BEGIN CERTIFICATE-----\n..."],
  "serial_number": "1A:2B:3C:4D:5E:6F:...",
  "not_after": "2026-04-30T14:00:00Z"
}
```

**The private key is returned exactly once.** Save it securely — ScaiVault doesn't keep a plaintext copy by default. If the role is configured with `store_private_keys: true`, you can retrieve it later via `POST /v1/pki/certificates/{id}/private-key`.

## Sign a CSR

When a peer generates their own key, they send you a CSR. Sign it directly:

```bash
curl -X POST https://scaivault.scailabs.ai/v1/pki/sign/svc-mtls \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "csr_pem": "-----BEGIN CERTIFICATE REQUEST-----\n...",
    "ttl": "168h"
  }'
```

Response contains `certificate`, `ca_chain`, `serial_number`, `not_after` — no private key.

## CSR workflow (with approval)

For CSRs that need human review before signing:

```bash
# 1. Import the CSR
curl -X POST https://scaivault.scailabs.ai/v1/pki/csr/import \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"csr_pem": "-----BEGIN CERTIFICATE REQUEST-----\n..."}'
# → {"id": "csr_abc", "status": "pending", ...}

# 2. Review what was requested
curl -H "Authorization: Bearer $TOKEN" \
     https://scaivault.scailabs.ai/v1/pki/csr/csr_abc

# 3. Approve or reject
curl -X POST https://scaivault.scailabs.ai/v1/pki/csr/csr_abc/approve \
  -H "Authorization: Bearer $TOKEN"

# 4. Sign against a CA
curl -X POST https://scaivault.scailabs.ai/v1/pki/csr/csr_abc/sign \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ca_id": "ca_intermediate_mtls", "validity_days": 90}'
```

Alternatively, generate the CSR in ScaiVault itself (private key stays in the vault):

```bash
curl -X POST https://scaivault.scailabs.ai/v1/pki/csr \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": {"common_name": "billing.svc.cluster.local"},
    "san_dns": ["billing-api.svc.cluster.local"],
    "key_type": "ec",
    "key_size": 256
  }'
```

Reject a CSR with reason:

```bash
curl -X POST https://scaivault.scailabs.ai/v1/pki/csr/csr_abc/reject \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason": "CN not in allowed scope"}'
```

## List issued certificates

```bash
curl -H "Authorization: Bearer $TOKEN" \
     "https://scaivault.scailabs.ai/v1/pki/certificates?ca_id=ca_intermediate_mtls&expiring_within=30d"
```

Response:

```json
{
  "data": [
    {
      "id": "cert_abc",
      "common_name": "billing.svc.cluster.local",
      "issuer_ca_id": "ca_intermediate_mtls",
      "serial_number": "1A:2B:...",
      "valid_from": "2026-04-23T14:00:00Z",
      "valid_until": "2026-04-30T14:00:00Z",
      "is_revoked": false
    }
  ],
  "cursor": null,
  "has_more": false
}
```

## Validate a certificate chain

```bash
curl -X POST https://scaivault.scailabs.ai/v1/pki/certificates/validate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "certificate_pem": "-----BEGIN CERTIFICATE-----\n...",
    "chain_pem": "-----BEGIN CERTIFICATE-----\n...",
    "check_revocation": true
  }'
```

Response:

```json
{
  "valid": true,
  "chain_valid": true,
  "not_expired": true,
  "not_revoked": true,
  "trusted_anchor": "trust_anchor_xyz"
}
```

## Revoke a certificate

```bash
curl -X POST https://scaivault.scailabs.ai/v1/pki/revoke \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"serial_number": "1A:2B:3C:...", "reason": "key_compromise"}'
```

Reasons (RFC 5280): `unspecified`, `key_compromise`, `ca_compromise`, `affiliation_changed`, `superseded`, `cessation_of_operation`, `certificate_hold`, `privilege_withdrawn`, `aa_compromise`.

## CRL

Get the current CRL for a CA:

```bash
curl https://scaivault.scailabs.ai/v1/pki/ca/ca_intermediate_mtls/crl
```

Returns `application/pkix-crl` (DER) or PEM depending on `Accept` header.

Force immediate regeneration (default is hourly):

```bash
curl -X POST https://scaivault.scailabs.ai/v1/pki/ca/ca_intermediate_mtls/crl \
  -H "Authorization: Bearer $TOKEN"
```

## Trust anchors

Register an external CA certificate you want to trust:

```bash
curl -X POST https://scaivault.scailabs.ai/v1/pki/trust-anchors \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "vendor-xyz-root",
    "certificate_pem": "-----BEGIN CERTIFICATE-----\n..."
  }'
```

List and delete:

```bash
curl -H "Authorization: Bearer $TOKEN" https://scaivault.scailabs.ai/v1/pki/trust-anchors
curl -X DELETE -H "Authorization: Bearer $TOKEN" https://scaivault.scailabs.ai/v1/pki/trust-anchors/trust_anchor_xyz
```

Trust anchors are consulted by `POST /v1/pki/certificates/validate`.

## Renew a certificate

```bash
curl -X POST https://scaivault.scailabs.ai/v1/pki/certificates/cert_abc/renew \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ttl": "168h"}'
```

Issues a new cert with the same subject and SANs, returns the new PEM + key. The old cert stays valid until its `not_after`.

## Import an existing certificate

For certificates issued elsewhere that you want to track in ScaiVault:

```bash
curl -X POST https://scaivault.scailabs.ai/v1/pki/certificates/import \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "certificate_pem": "-----BEGIN CERTIFICATE-----\n...",
    "private_key_pem": "-----BEGIN PRIVATE KEY-----\n...",
    "chain_pem": "-----BEGIN CERTIFICATE-----\n..."
  }'
```

## What's next

- [ACME](./acme) — public certificates from Let's Encrypt et al.
- [PKI](../core-concepts/pki) — conceptual model.
- [PKI Reference](../reference/pki) — all endpoints.
