English version coming soon

The full public documentation is currently available in French only. English translation is in progress.

Section 03PKIFactorDevOps

API reference

OpenAPI 3.1 REST API — idempotent, versioned, automation-ready.

Last updated 2026-04-18

L'API REST de PKIFactor est documentée au format OpenAPI 3.1 et exposée sur /api/v1/. Elle est :

  • Idempotente sur les opérations critiques (émission, révocation) via en-tête Idempotency-Key
  • Versionnée par préfixe d'URL (/api/v1/, /api/v2/)
  • Paginée par curseur opaque (?cursor=...&limit=50) ou par offset (?offset=0&limit=50)
  • Filtrable par DSL léger (?filter=status:issued,cn:*.acme.com)
Idempotency

Toute opération d'émission ou de révocation peut être rejouée en toute sécurité avec le même en-tête Idempotency-Key. PKIFactor retournera la même réponse pendant 24h.

3.1. Authentification

PKIFactor supporte trois mécanismes.

3.1.1. API Keys (recommandé pour M2M)

bash
# Création via UI : Settings → API Keys → Generate
# Format : pkf_<32_chars_alphanum>

curl https://pki.exemple.com/api/v1/certificates \
  -H "Authorization: Bearer pkf_a1b2c3d4e5f6..."

Les clés API sont scopées par organisation et peuvent être restreintes à un sous-ensemble de permissions (certificates:read, certificates:issue, templates:write, etc.).

3.1.2. mTLS (recommandé pour CI/CD haute sécurité)

bash
# Le client présente un certificat émis par PKIFactor lui-même
curl https://pki.exemple.com/api/v1/certificates \
  --cert client.pem \
  --key client.key \
  --cacert ca-chain.pem

3.1.3. OAuth 2.0 / OIDC (recommandé pour les utilisateurs)

bash
# 1. Obtention d'un token via votre IdP (Keycloak, Okta, Azure AD, Google)
TOKEN=$(curl -X POST https://idp.exemple.com/realms/acme/protocol/openid-connect/token \
  -d "grant_type=password" \
  -d "client_id=pkifactor" \
  -d "username=alice@acme.com" \
  -d "password=*****" \
  | jq -r .access_token)

# 2. Utilisation du token Bearer
curl https://pki.exemple.com/api/v1/certificates \
  -H "Authorization: Bearer $TOKEN"

3.2. Endpoints principaux

3.2.1. Certificats

MéthodeEndpointDescription
POST/api/v1/certificatesÉmettre un certificat
GET/api/v1/certificatesLister (filtres + pagination)
GET/api/v1/certificates/{id}Détail
GET`/api/v1/certificates/{id}/download?fmt=pemder
POST/api/v1/certificates/{id}/renewRenouveler
POST/api/v1/certificates/{id}/revokeRévoquer
bash
# Lister les certificats expirant dans les 30 jours pour une org donnée
curl "https://pki.exemple.com/api/v1/certificates?filter=org:1,status:issued,expires_lt:30d&limit=100" \
  -H "Authorization: Bearer $TOKEN"
json
{
  "data": [
    {
      "id": 1024,
      "serial": "3F:A1:B2:C3:...",
      "subject": { "common_name": "api.acme.com" },
      "sans": ["api.acme.com", "api-v2.acme.com"],
      "status": "issued",
      "not_before": "2026-01-15T08:00:00Z",
      "not_after":  "2026-04-15T08:00:00Z",
      "pki": { "id": 1, "name": "Vault PKI", "type": "vault" },
      "template": { "id": 5, "code": "web-tls" }
    }
  ],
  "pagination": {
    "next_cursor": "eyJpZCI6MTAyNH0=",
    "total": 147
  }
}

3.2.2. Templates CLM

bash
# Création d'un template avec ZetaCA post-quantum
curl -X POST https://pki.exemple.com/api/v1/clm/templates \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "PQ Server (ML-DSA-87)",
    "code": "pq-server",
    "organization_id": 1,
    "pki_id": 3,
    "pki_role": "pq-srv",
    "key_algorithm": "ML_DSA_87",
    "validity_days": 365,
    "key_usage": ["digital_signature"],
    "extended_key_usage": ["server_auth"],
    "requires_approval": true,
    "approver_role": "pki-approver"
  }'

3.2.3. Audit logs

bash
curl "https://pki.exemple.com/api/v1/audit?event=certificate.issued&since=2026-04-01" \
  -H "Authorization: Bearer $TOKEN"
json
{
  "data": [
    {
      "id": "01J...",
      "ts": "2026-04-18T10:02:34.123Z",
      "actor": { "type": "user", "id": 12, "email": "alice@acme.com" },
      "event": "certificate.issued",
      "resource": { "type": "certificate", "id": 1024, "serial": "3F:A1:B2:..." },
      "metadata": {
        "template_code": "web-tls",
        "pki_id": 1,
        "ip": "203.0.113.42"
      },
      "signature": "ed25519:..."
    }
  ]
}
Aperçu API Explorer — Swagger / Redoc style
POST/api/v1/certificates

Issue a new certificate

Submit a template code + subject; returns a signed X.509 certificate.

Request body (application/json)
template_codestringrequired
subjectobjectrequired
sansarray
key_formatenum
delivery_formatenum
Responses
201Created
400Bad Request
422Validation Error

3.3. Pagination, filtres, erreurs

Pagination

bash
GET /api/v1/certificates?cursor=eyJpZCI6MTAwMH0=&limit=50

Le curseur est opaque et stable. Préférer le curseur à l'offset pour les grands volumes.

Filtres (DSL)

ini
?filter=status:issued
?filter=status:issued,cn:*.acme.com
?filter=expires_lt:30d
?filter=tag:prod,tag:critical

Opérateurs supportés : : (égalité), _lt (inférieur), _gt (supérieur), _in (liste), * (wildcard).

Erreurs

Toutes les erreurs suivent le format RFC 7807 (Problem Details) :

json
{
  "type": "https://docs.zetacert.com/errors/template-not-found",
  "title": "Template not found",
  "status": 404,
  "detail": "Template with code 'web-tls' does not exist in organization 'ACME'",
  "instance": "/api/v1/certificates",
  "trace_id": "01J5..."
}
CodeSignification
400Requête malformée
401Authentification manquante ou invalide
402Fonctionnalité non couverte par la licence
403Accès refusé (permissions ou organisation)
404Ressource introuvable
409Conflit (idempotency, contrainte unique)
422Validation échouée
429Rate limit dépassé
5xxErreur serveur

3.4. Exemples SDK

Python

python
# pip install zetacert-sdk
from zetacert import PKIFactorClient

client = PKIFactorClient(
    base_url="https://pki.exemple.com",
    api_key="pkf_a1b2c3d4e5f6...",
)

cert = client.certificates.issue(
    template_code="web-tls",
    common_name="www.acme.com",
    sans=["www.acme.com", "acme.com"],
)

print(f"Certificat émis : {cert.serial}")
print(f"Expire le : {cert.not_after}")

# Téléchargement au format PEM
with open("www.acme.com.pem", "wb") as f:
    f.write(client.certificates.download(cert.id, fmt="pem"))

Go

go
// go get github.com/zetacert/pkifactor-go
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/zetacert/pkifactor-go"
)

func main() {
    client := pkifactor.NewClient(
        pkifactor.WithBaseURL("https://pki.exemple.com"),
        pkifactor.WithAPIKey("pkf_a1b2c3d4e5f6..."),
    )

    cert, err := client.Certificates.Issue(context.Background(), &pkifactor.IssueRequest{
        TemplateCode: "web-tls",
        CommonName:   "www.acme.com",
        SANs:         []string{"www.acme.com", "acme.com"},
    })
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Certificat émis : %s\n", cert.Serial)
}

TypeScript

typescript
// npm install @zetacert/pkifactor-sdk
import { PKIFactorClient } from "@zetacert/pkifactor-sdk";

const client = new PKIFactorClient({
  baseUrl: "https://pki.exemple.com",
  apiKey: process.env.PKIFACTOR_API_KEY!,
});

const cert = await client.certificates.issue({
  templateCode: "web-tls",
  commonName: "www.acme.com",
  sans: ["www.acme.com", "acme.com"],
});

console.log(`Certificat émis : ${cert.serial}`);
console.log(`Expire le : ${cert.notAfter}`);