REST API¶
Everything the web interface does goes through PKIFactor's REST API: requests, approvals, issuance, renewal, revocation, downloads, inventory, profiles, authorities, devices, users, organizations, settings. The API is described by an OpenAPI schema and documented interactively.
Where the API lives¶
| Address | Content |
|---|---|
https://clm.example.com/api/v1/ |
The complete API. /api/v1/<route> calls the route <route> of the schema, for example /api/v1/certificates/request |
https://clm.example.com/api/v1/docs |
Interactive documentation (Swagger UI): every route, its parameters, its responses, and a Try it out button |
https://clm.example.com/api/v1/redoc |
The same documentation, as a readable reference (ReDoc) |
https://clm.example.com/api/v1/openapi.json |
The OpenAPI schema, to generate clients |
Routes whose name already starts with /api/ (for example /api/auth/login, /api/admin/license, /api/integrations/servicenow/...) are also served at that address directly.
The documentation can be hidden in hardened deployments with API_DOCS_ENABLED=false; the API itself stays available.
Authentication¶
Every route requires authentication, except the health checks and the documentation. An anonymous call returns 401.
Session (users and scripts acting as a user)¶
curl -s -c jar -X POST https://clm.example.com/api/auth/login \
--data-urlencode "email=alice@example.com" --data-urlencode "password=$PKF_PASSWORD"
curl -s -b jar https://clm.example.com/api/v1/certificates/my
The login sets a session cookie (session, HTTP-only, SameSite=Lax) valid for the Session Timeout set in System > Settings (30 minutes by default; JWT_EXPIRE_SECONDS only when the setting cannot be read). The login response gives the duration in seconds (expires_in). The web interface renews it while the user stays active, and a session never lasts more than 12 hours after sign-in (JWT_MAX_SESSION_SECONDS), renewals included. A script keeps the cookie in a file, sends it with every call, and signs in again when it receives 401. The rights are exactly those of the user: role, organizations, teams.
Sign-in failures count towards the account lockout set in System > Settings (5 consecutive failures lock the account for 15 minutes by default). An account whose password must be changed receives 403 Password change required on every route until the password is changed. When client certificate authentication is set to required, a password alone is refused (403).
Accounts that sign in with single sign-on or a client certificate use the same cookie after signing in through the browser; for automation, prefer a local account dedicated to the script, with the narrowest role.
API keys (protocol gateways and integrations)¶
Users and scripts acting as a user authenticate only with the session cookie: user routes do not accept an Authorization: Bearer token.
Two kinds of callers use a dedicated key (Authorization: Bearer ...) instead, on their own routes only:
- the ACME, EST, SCEP and CMP gateways, each with the key of its service account. These keys are deployment secrets shared between the backend and the gateway, set at installation; they are not managed from the web interface;
- the ServiceNow catalogue integration, with a key generated, replaced and revoked by an administrator in System > Settings > ServiceNow integration. See ServiceNow.
Examples¶
These examples were run against the laboratory platform; replace the address, the profile and the e-mail addresses with yours.
Request a certificate with a CSR
python3 - > request.json <<'EOF'
import json
print(json.dumps({"clm_template_id": 1,
"common_name": "web01.example.com",
"san": "web01.example.com",
"team_email": "web-team@example.com",
"csr_pem": open("web01.csr").read()}))
EOF
curl -s -b jar -X POST https://clm.example.com/api/v1/certificates/request \
-H "Content-Type: application/json" -d @request.json
{"success": true, "message": "Certificat émis", "certificate": {"id": 490, "status": "ISSUED"}, "details": null}
clm_template_id (the profile) and team_email are required. Without csr_pem, PKIFactor generates the key and needs p12_password (at least 8 characters); with csr_pem, p12_password is refused. Subject fields that are missing from the request are taken from the CSR; a field that the profile does not allow you to set is refused with its name. key_algorithm chooses the key when PKIFactor generates it (default RSA 2048, within the algorithms the profile allows).
When the profile is open to several organizations and you belong to more than one of them, add organization_id; otherwise PKIFactor picks your only eligible organization.
If the profile requires approval, the status is PENDING_APPROVAL; for a public authority that validates the domain first, it is PENDING_DCV. The response messages are currently in French (Certificat émis, Demande enregistrée (en attente d'approbation)). This route accepts 10 requests per minute per client address.
List my certificates
curl -s -b jar "https://clm.example.com/api/v1/certificates/my?search=web01&limit=50"
{"total": 1, "limit": 50, "offset": 0, "results": [{"id": 490, "common_name": "web01.example.com", "status": "ISSUED", "...": "..."}]}
status, search (common name), serial_number, template_id, expires_before and expires_after (YYYY-MM-DD), latest=true (hide versions already renewed), weak=true (RSA keys under 2048 bits or SHA-1/MD5 signatures), limit (1 to 200, default 50), offset. scope=mine (default) returns your certificates and those of your teams; scope=org returns those of your organizations, read only.
Revoke
curl -s -b jar -X POST https://clm.example.com/api/v1/certificates/490/revoke \
--data-urlencode "reason=cessationOfOperation"
keyCompromise, affiliationChanged, superseded, cessationOfOperation, privilegeWithdrawn, unspecified); any other text is recorded as the reason in PKIFactor and sent to the authority as unspecified. If the profile requires approval, the revocation waits for an approver.
Behaviour¶
| Topic | Rule |
|---|---|
| Errors | JSON body {"detail": ...}; 401 not authenticated, 403 not allowed, 404 unknown or not visible to you, 409 conflict (for example a duplicate name), 422 invalid input, 429 rate limited |
| Visibility | A resource you may not see answers 404, not 403, so that its existence is not disclosed |
| Rate limiting | Applied by nginx per client address, on sign-in (10 attempts per minute) and on certificate requests (10 per minute) |
| Cross-site protection | A state-changing call made with the session cookie and an Origin (or Referer) header naming another site is refused (403). Scripts that send no such header, and calls with a Bearer key, are not affected |
| Traceability | Every response carries a request identifier in the X-Trace-ID header; send X-Request-ID to set your own. A server error returns a reference that support can trace in the logs (Logging and incident triage) |
| Audit | Every state change is recorded in the audit trail with the user and the request identifier |
| Versioning | /api/v1 is stable within a major version; additions are backward compatible. Changes are listed in the Release notes |