Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Admin API and Operator Management

API Reference — This page documents the admin REST API for operators and automation tools. For the akamuctl CLI that wraps this API, see akamuctl — Admin CLI in the Operator Guide.

The akamu admin API exposes management endpoints for operators on the same listener as the main ACME API. It requires operator authentication on every request. When the [admin] section is absent from the configuration file, all admin endpoints return 404 Not Found and are unreachable.

flowchart LR
    AC([ACME client]) -->|"HTTPS :8443<br/>/acme/*"| SRV["Akamu<br/>shared listener<br/>:8443"]
    OP([Operator]) -->|"HTTPS :8443<br/>/admin/*<br/>(mTLS / GSSAPI)"| SRV
    SRV --- CORE["Akamu core<br/>(DB, CA, audit)"]

    classDef client fill:#eff6ff,stroke:#3b82f6,color:#0f172a
    classDef listen fill:#fefce8,stroke:#ca8a04,color:#0f172a
    classDef core   fill:#f0fdf4,stroke:#16a34a,color:#0f172a
    class AC,OP client
    class SRV listen
    class CORE core

See akamuctl for the command-line tool that wraps this API. See Configuration Reference — [admin] for all configuration keys.

Authentication

Every request to the admin API must be authenticated. Four mechanisms are supported.

mTLS client certificate

The client presents a certificate during the TLS handshake. The server computes the SHA-256 fingerprint of the DER-encoded leaf certificate and looks it up in the operators table. On success, the server issues a session token and returns it in the response body under session_token and in the X-Session-Token response header.

Proxy-forwarded client certificate

When Akamu runs behind a TLS-terminating reverse proxy, the proxy can forward the verified client certificate in an HTTP header. Configure [admin.proxy_auth] with the trusted proxy CIDR ranges and the header convention (Nginx, Apache, or Envoy XFCC). The server extracts the certificate from the header, computes the fingerprint, and issues a session token — identical to the direct mTLS path. Audit events record this as "method":"cert-proxy". See Configuration Reference for details.

GSSAPI/Kerberos

The client sends an Authorization: Negotiate <base64-SPNEGO-token> header. The server validates the token against the keytab configured in [admin.gssapi], extracts the Kerberos principal, and looks it up in the operators table. On success the server issues a session token (same as the mTLS path) and may include a GSSAPI continuation token in a WWW-Authenticate: Negotiate <token> response header.

Bearer session token

After a successful mTLS, proxy-forwarded cert, or GSSAPI login, the client passes the returned token as Authorization: Bearer <token> on subsequent requests. The server looks up the token in its in-memory session store and refreshes the idle timer. Tokens that have been idle for longer than session_ttl_secs (default 1 hour) are expired and the client receives 401 Unauthorized.

The session store is bounded at 1 000 active sessions. When the cap is reached, the least-recently-active session is evicted.

Token comparisons use constant-time equality to prevent timing side-channels.

Roles

Each operator has exactly one role that determines which admin endpoints they may call. For a full description of each role, its capabilities, restrictions, and the complete route-by-role permission matrix, see Operator Roles.

Endpoint reference

All paths are relative to the server’s base_url. Admin endpoints are served on the same listener as the ACME API.

POST /admin/session

Authenticate and obtain a session token. The request must carry one of the three credential types described above.

Response 200 OK:

{
  "session_token": "a4f1…64-hex-chars…",
  "role": "auditor",
  "expires_at": "2026-05-02T14:00:00Z"
}

The token is also returned in the X-Session-Token response header.

DELETE /admin/session

Invalidate the current session token. The server removes the token from its in-memory store, records an admin.logout audit event, and returns a Set-Cookie: session=; Max-Age=0 header that instructs the browser to immediately expire the session cookie set at login.

Response: 204 No Content.

GET /admin/operators

List all registered operators, including deactivated ones.

Query parameters: limit (1–1000, default 1000), offset (default 0).

Response 200 OK:

{
  "operators": [
    {
      "id": 1,
      "name": "alice",
      "role": "administrator",
      "cert_fingerprint": "a3b4c5…",
      "gssapi_principal": null,
      "created_at": "2026-05-01T09:00:00Z",
      "last_seen_at": "2026-05-02T08:30:00Z",
      "active": true,
      "failed_attempts": 0,
      "locked_until": null
    }
  ]
}

POST /admin/operators

Register a new operator. At least one of cert_fingerprint or gssapi_principal must be provided.

Request body:

{
  "name": "bob",
  "role": "auditor",
  "cert_fingerprint": "b2c3d4…",
  "gssapi_principal": null
}

cert_fingerprint is the lowercase hex SHA-256 digest of the DER-encoded client certificate leaf. The akamuctl operator add --cert-file command computes this automatically.

Response 201 Created:

{ "name": "bob", "created_at": "2026-05-02T10:00:00Z" }

Returns 409 Conflict when an operator with the same fingerprint or principal already exists.

GET /admin/operators/{id}

Show a single operator’s details.

Response 200 OK:

{
  "id": 3,
  "name": "alice",
  "role": "administrator",
  "cert_fingerprint": "a3b4c5…",
  "gssapi_principal": null,
  "created_at": "2026-05-01T09:00:00Z",
  "last_seen_at": "2026-05-02T08:30:00Z",
  "active": true,
  "failed_attempts": 0,
  "locked_until": null
}

Returns 404 Not Found when the ID does not exist.

PUT /admin/operators/{id}

Update operator fields. Only provided fields are changed; omitted fields remain unchanged.

Request body:

{
  "name": "Alice Smith",
  "role": "ca_operations",
  "cert_fingerprint": "d4e5f6…",
  "gssapi_principal": "alice@NEWREALM.COM"
}

All fields are optional. role must be one of administrator, ca_operations, ca_ra, or auditor when provided.

Response: 204 No Content on success, 404 Not Found when the ID does not exist.

PATCH /admin/operators/{id}

Update the active status or ca_id scope of an operator.

Request body:

{ "active": false }

or, to assign a CA scope to a ca_ra operator:

{ "ca_id": "rsa" }

Set active to false to deactivate, true to reactivate. Deactivating an operator immediately invalidates all of that operator’s active session tokens.

When ca_id is provided, the operator’s CA scope is updated. Setting role = "ca_ra" without also providing a non-empty ca_id (either in this request or already stored) is rejected with 422 Unprocessable Entity.

Response: 204 No Content on success, 404 Not Found when the ID does not exist.

POST /admin/operators/{id}/unlock

Reset the operator’s failed-authentication counter and clear the lockout timestamp (FIA_AFL.1). Use this when an operator has been locked out due to exceeding max_failed_auth.

Response: 204 No Content on success, 404 Not Found when the ID does not exist.

GET /admin/audit

Query the structured audit event log. See Audit Trail for details on the event taxonomy.

Query parameters:

ParameterDescription
typeFilter by event type string (e.g. cert.issue).
subjectFilter by subject (account UUID, certificate serial, JWK thumbprint, etc.).
fromRFC 3339 lower bound for occurred_at.
untilRFC 3339 upper bound for occurred_at.
outcomesuccess or failure.
limit1–1000, default 100.
offsetDefault 0.

Results are ordered newest-first.

Response 200 OK:

{
  "events": [
    {
      "occurred_at": "2026-05-02T08:30:00Z",
      "event_type": "cert.issue",
      "subject": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "principal": "acme:xZ9gF…",
      "outcome": "success",
      "detail": "{\"profile\":\"tlsserver\"}"
    }
  ],
  "total_since_startup": 5000,
  "limit": 100,
  "offset": 0
}

Error responses:

StatusCondition
400 Bad RequestThe from or until parameter is not a valid RFC 3339 timestamp. The response body includes a detail field describing the error.
500 Internal Server ErrorThe audit backend (journal namespace socket, JSONL file, or journalctl subprocess) is inaccessible. The response body includes {"status": 500, "detail": "journal query error"}.

GET /admin/profiles

List all loaded certificate profiles with their parameters.

Response 200 OK:

{
  "profiles": [
    {
      "id": "tlsserver",
      "description": "TLS server certificate",
      "validity_days": 90,
      "hash_alg": "SHA256",
      "extended_key_usages": ["serverAuth"],
      "issue_as_mtc": false
    }
  ]
}

GET /admin/profiles/{id}

Return a single certificate profile by ID.

Response 200 OK:

{
  "id": "codesigning",
  "description": "Code signing certificate",
  "validity_days": 365,
  "hash_alg": "SHA256",
  "key_usage_bits": null,
  "extended_key_usages": ["code_signing"],
  "crl_url": null,
  "ocsp_url": null,
  "allowed_key_types": null,
  "certificate_policies": null,
  "issue_as_mtc": false,
  "allowed_identifier_patterns": null,
  "identifier_match_all": false,
  "auth_hook": null,
  "auth_hook_timeout_secs": null,
  "require_account_grant": true,
  "ca_ids": null
}

Returns 404 Not Found when no profile with the given ID is loaded.

Requires any authenticated role.

POST /admin/profiles

Add a new certificate profile to the runtime cache (FPT_NPE_EXT.1). Requires the administrator role.

Request body:

{
  "id": "codesigning",
  "description": "Code signing certificate",
  "validity_days": 365,
  "hash_alg": "sha256",
  "extended_key_usages": ["code_signing"],
  "require_account_grant": true
}

All fields except id are optional and have defaults (90 days validity, sha256 hash, no extended key usage restriction). Returns 409 Conflict when a profile with the same id already exists.

Response 201 Created:

{ "id": "codesigning", "description": "Code signing certificate" }

PUT /admin/profiles/{id}

Replace an existing certificate profile in the runtime cache (FPT_NPE_EXT.1). The profile is identified by {id} in the URL path; the request body uses the same schema as POST /admin/profiles but without the id field. Requires the administrator role.

Response: 204 No Content on success, 404 Not Found when the profile does not exist.

DELETE /admin/profiles/{id}

Remove a certificate profile from the runtime cache (FPT_NPE_EXT.1). Requires the administrator role.

Response: 204 No Content on success, 404 Not Found when the profile does not exist.

GET /admin/accounts

List ACME accounts with optional filtering and pagination.

Query parameters:

ParameterDescription
ca_idFilter by CA ID. Only accounts registered via the named CA’s new-account endpoint are returned.
statusFilter by account status (valid or deactivated).
limit1–1000, default 100.
offsetDefault 0.

Response 200 OK:

{
  "accounts": [
    {
      "id": "d290f1ee-…",
      "status": "valid",
      "contact": "[\"mailto:admin@example.com\"]",
      "jwk_thumbprint": "xZ9gF…",
      "created": 1746154800,
      "updated": 1746241200,
      "profile_grants": "[\"tlsserver\"]"
    }
  ],
  "limit": 100,
  "offset": 0
}

GET /admin/account/{id}

Show a single account’s details.

Response 200 OK:

{
  "id": "d290f1ee-…",
  "status": "valid",
  "contact": "[\"mailto:admin@example.com\"]",
  "jwk_thumbprint": "xZ9gF…",
  "created": 1746154800,
  "updated": 1746241200,
  "profile_grants": "[\"tlsserver\"]"
}

Returns 404 Not Found when the account does not exist.

POST /admin/account/{id}/deactivate

Admin-initiated account deactivation. Sets the account status to deactivated. The account can no longer create orders or issue certificates.

Response: 204 No Content on success, 404 Not Found when the account does not exist.

GET /admin/account/{id}/profile-grants

Return the profile grant list for account {id}. null means the account has no restrictions and may request any profile.

Response 200 OK:

{ "profile_grants": ["tlsserver", "codesigning"] }

or

{ "profile_grants": null }

PUT /admin/account/{id}/profile-grants

Replace the account’s profile grant list.

Request body:

{ "profile_grants": ["tlsserver"] }

Response: 204 No Content.

DELETE /admin/account/{id}/profile-grants

Clear all profile restrictions. Sets profile_grants to null (unrestricted).

Response: 204 No Content.

GET /admin/certs

Search the certificate table.

Query parameters: ca_id (filter by CA ID), serial, subject (subject DN substring match), account_id, after (RFC 3339), before (RFC 3339), status (active or revoked), limit (1–1000, default 100), offset.

Response 200 OK:

{
  "certs": [
    {
      "id": "3fa85f64-…",
      "account_id": "d290f1ee-…",
      "serial_number": "0a1b2c3d",
      "status": "active",
      "not_before": "2026-05-01T00:00:00Z",
      "not_after": "2026-07-30T00:00:00Z",
      "revoked_at": null,
      "revocation_reason": null
    }
  ],
  "limit": 100,
  "offset": 0
}

GET /admin/certs/{id}

Show a single certificate’s metadata. Does not return the PEM or DER content (use the download endpoint for that).

Response 200 OK:

{
  "id": "3fa85f64-…",
  "order_id": "7b2e1a3f-…",
  "account_id": "d290f1ee-…",
  "serial_number": "0a1b2c3d",
  "status": "active",
  "not_before": "2026-05-01T00:00:00Z",
  "not_after": "2026-07-30T00:00:00Z",
  "revoked_at": null,
  "revocation_reason": null,
  "mtc_log_index": null,
  "created": 1746154800,
  "suggested_window_start": 1750000000,
  "suggested_window_end": 1751000000,
  "replaced_by": null
}

Returns 404 Not Found when the certificate does not exist.

GET /admin/certs/{id}/download

Download a certificate’s content as PEM or DER.

Query parameters:

ParameterDescription
formatpem (default) or der.

Response 200 OK:

  • PEM format: Content-Type: application/pem-certificate-chain
  • DER format: Content-Type: application/pkix-cert

Returns 404 Not Found when the certificate does not exist.

POST /admin/eab

Provision a new External Account Binding key. Requires the administrator or ca_operations role.

Request body:

{
  "kid": "my-device-001",
  "hmac_key_b64u": "c2VjcmV0LWhtYWMta2V5LWJ1ZmZlcg",
  "profile_grants": ["tlsserver"],
  "alg": "sha256",
  "for_operator_id": 3
}
FieldRequiredDescription
kidYesUnique key identifier string.
hmac_key_b64uYesBase64url-encoded raw HMAC key bytes (no padding).
profile_grantsNoArray of profile names the EAB key pre-authorizes. Omit or set to null for an unrestricted key.
algNoHMAC algorithm: "sha256" (default), "sha384", or "sha512".
for_operator_idNoAdministrator only. When set, created_by_operator_id on the new key is set to this operator ID instead of the calling operator. This controls which operator the key is associated with for POST /admin/session/eab web UI login.

EAB keys are server-global and are not bound to any CA, even when created by a scoped ca_operations operator. Only administrator may set for_operator_id; a ca_operations caller that includes it receives 403 Forbidden.

Returns 409 Conflict when the kid already exists.

Response 201 Created:

{ "kid": "my-device-001", "created": 1746154800, "alg": "sha256" }

GET /admin/eab/{kid}

Show a single EAB key’s details.

Response 200 OK:

{
  "kid": "my-device-001",
  "created": 1746154800,
  "used_at": null,
  "profile_grants": "[\"tlsserver\"]"
}

Returns 404 Not Found when the key does not exist.

DELETE /admin/eab/{kid}

Deactivate an EAB key. The key is removed from the table; any previously issued HMAC credentials for this kid are permanently invalidated.

Response: 204 No Content, 404 Not Found when the key does not exist.

GET /admin/eab

List EAB keys.

Query parameters: used (true/false to filter by usage status), limit (1–1000, default 200), offset.

Response 200 OK:

{
  "eab_keys": [
    {
      "kid": "my-device-001",
      "created": 1746154800,
      "used_at": null,
      "profile_grants": "[\"tlsserver\"]"
    }
  ]
}

GET /admin/orders

List certificate orders with optional filtering and pagination.

Query parameters:

ParameterDescription
ca_idFilter by CA ID.
account_idFilter by account UUID.
statusFilter by order status (pending, ready, processing, valid, invalid).
limit1–1000, default 100.
offsetDefault 0.

Response 200 OK:

{
  "orders": [
    {
      "id": "7b2e1a3f-…",
      "account_id": "d290f1ee-…",
      "status": "valid",
      "identifiers": "[{\"type\":\"dns\",\"value\":\"example.com\"}]",
      "certificate_id": "3fa85f64-…",
      "profile": "tlsserver",
      "created": 1746154800,
      "updated": 1746241200,
      "expires": 1746760800
    }
  ],
  "limit": 100,
  "offset": 0
}

GET /admin/orders/{id}

Show a single order’s details, including authorization IDs.

Response 200 OK:

{
  "id": "7b2e1a3f-…",
  "account_id": "d290f1ee-…",
  "status": "valid",
  "identifiers": "[{\"type\":\"dns\",\"value\":\"example.com\"}]",
  "certificate_id": "3fa85f64-…",
  "profile": "tlsserver",
  "created": 1746154800,
  "updated": 1746241200,
  "expires": 1746760800,
  "not_before": null,
  "not_after": null,
  "replaces": null,
  "authorization_ids": ["a1b2c3d4-…", "e5f6a7b8-…"]
}

Returns 404 Not Found when the order does not exist.

GET /admin/config

Show the server’s redacted runtime configuration. Sensitive values such as the database URL are masked.

Response 200 OK:

{
  "base_url": "https://acme.example.com",
  "db_url": "***",
  "mtc_enabled": false,
  "caa_identities": ["example.com"],
  "validate_dnssec": true
}

POST /admin/crl/force

Force immediate CRL regeneration. The cached CRL is invalidated so the next GET /ca/crl request produces a fresh CRL reflecting all current revocations.

Response: 204 No Content.

POST /admin/revoke

Revoke a certificate by its internal ID.

Request body:

{ "cert_id": "3fa85f64-…", "reason": 1 }

reason is an RFC 5280 reason code (0 = unspecified, 1 = keyCompromise, 3 = affiliationChanged, 4 = superseded, 5 = cessationOfOperation, etc.). Revocation immediately invalidates the CRL cache.

Response: 204 No Content, 404 Not Found when the certificate is not found or is already revoked.

GET /admin/stats

Return live server statistics. All authenticated roles may call this endpoint.

Response 200 OK:

{
  "server_version": "0.1.0",
  "uptime_secs": 3600,
  "accounts": { "total": 42, "active": 40 },
  "certs":    { "total": 200, "active": 180, "revoked": 20 },
  "eab_keys": { "total": 10, "used": 8, "unused": 2 },
  "audit_events": { "since_startup": 5000 }
}

CA management endpoints

GET /admin/cas

List all configured CAs.

Response 200 OK:

{
  "cas": [
    {
      "id": "rsa",
      "is_default": true,
      "key_type": "rsa:4096",
      "hash_alg": "sha256",
      "crl_url": "http://acme.example.com/ca/rsa/crl",
      "ocsp_url": "http://acme.example.com/ca/rsa/ocsp"
    }
  ]
}

GET /admin/cas/{id}

Show details of a single CA including the CA certificate PEM.

Response 200 OK:

{
  "id": "rsa",
  "is_default": true,
  "key_type": "rsa:4096",
  "hash_alg": "sha256",
  "crl_url": "http://acme.example.com/ca/rsa/crl",
  "ocsp_url": "http://acme.example.com/ca/rsa/ocsp",
  "cert_pem": "-----BEGIN CERTIFICATE-----\n…\n-----END CERTIFICATE-----\n"
}

Returns 404 Not Found when the CA ID does not exist.

POST /admin/ca/{id}/crl/force

Force immediate CRL regeneration for the specified CA. The cached CRL is invalidated so the next GET /ca/{id}/crl request produces a fresh CRL reflecting all current revocations for that CA.

Requires the ca_operations or administrator role.

Response: 204 No Content. Returns 404 Not Found when the CA ID does not exist.

POST /admin/ca/{id}/cross-sign

Issue a cross-certificate: CA {id} (the issuer) signs the public key of another CA or an externally supplied certificate. The resulting cross-cert is stored in the database and retrievable via GET /admin/cross-certs/{id} and the public GET /ca/{subject_id}/cross-certs endpoint.

The issued cross-certificate always has pathLenConstraint = 0 — the subject CA cannot use it to issue further subordinate CAs.

Request body (exactly one of subject_ca_id or subject_cert_pem must be provided):

{ "subject_ca_id": "ec", "validity_years": 5 }

or

{ "subject_cert_pem": "-----BEGIN CERTIFICATE-----\n…", "validity_years": 5 }

Requires the administrator or ca_operations role.

Response 201 Created:

{ "id": "a1b2c3d4-…", "created_at": "2026-05-06T12:00:00Z" }

Returns 404 Not Found when the issuer CA ID or subject_ca_id does not exist.

GET /admin/cross-certs

List stored cross-certificates.

Query parameters:

ParameterDescription
issuer_ca_idFilter by issuing CA ID.
subject_ca_idFilter by subject CA ID.
limit1–1000, default 100.
offsetDefault 0.

Response 200 OK:

{
  "cross_certs": [
    {
      "id": "a1b2c3d4-…",
      "issuer_ca_id": "rsa",
      "subject_ca_id": "ec",
      "not_before": "2026-05-06T12:00:00Z",
      "not_after": "2031-05-06T12:00:00Z",
      "created_at": "2026-05-06T12:00:00Z"
    }
  ],
  "limit": 100,
  "offset": 0
}

GET /admin/cross-certs/{id}

Show a single cross-certificate by UUID, including its PEM.

Response 200 OK:

{
  "id": "a1b2c3d4-…",
  "issuer_ca_id": "rsa",
  "subject_ca_id": "ec",
  "not_before": "2026-05-06T12:00:00Z",
  "not_after": "2031-05-06T12:00:00Z",
  "created_at": "2026-05-06T12:00:00Z",
  "cert_pem": "-----BEGIN CERTIFICATE-----\n…\n-----END CERTIFICATE-----\n"
}

Returns 404 Not Found when the cross-cert ID does not exist.

MTC transparency log endpoints

These endpoints query and manage the Merkle Tree Certificate transparency log. They are only functional when MTC is enabled for the target CA ([mtc] in the server configuration). When MTC is not enabled, all endpoints return 404 Not Found with {"detail": "MTC not enabled for this CA"}.

All read-only endpoints accept an optional ca_id query parameter for multi-CA deployments. When omitted, the default CA is used. A ca_ra operator’s ca_id scope is automatically enforced on certificate-specific endpoints (inclusion-proof, standalone).

For the CLI that wraps these endpoints, see akamuctl — MTC transparency log.

GET /admin/mtc/tree-size

Return the current MTC log tree size.

Query parameters: ca_id (optional).

Requires the administrator, ca_operations, or auditor role.

Response 200 OK:

{ "tree_size": 42 }

GET /admin/mtc/root

Return the tree size and root hash.

Query parameters: ca_id (optional).

Requires the administrator, ca_operations, or auditor role.

Response 200 OK:

{
  "tree_size": 42,
  "root_hash": "a1b2c3d4e5f6…"
}

GET /admin/mtc/landmarks

List all landmarks as JSON.

Query parameters: ca_id (optional).

Requires the administrator, ca_operations, or auditor role.

Response 200 OK:

{
  "landmarks": [
    {
      "sequence_no": 1,
      "tree_size": 100,
      "created_at": "2026-07-01T00:00:00Z"
    }
  ],
  "total": 1
}

GET /admin/mtc/landmark-list

Return the landmark list in the spec section 3.4 text/plain format.

Query parameters: ca_id (optional).

Requires the administrator, ca_operations, or auditor role.

Response 200 OK: Content-Type: text/plain; charset=utf-8.

GET /admin/mtc/landmarks/{seq}/cert

Download the landmark certificate DER for the given sequence number.

Query parameters: ca_id (optional).

Requires the administrator or ca_operations role.

Response 200 OK: Content-Type: application/octet-stream (raw DER bytes).

Returns 503 Service Unavailable with {"detail": "landmark certificate not yet built"} if the certificate has not been constructed yet.

Returns 404 Not Found when the sequence number does not exist.

GET /admin/mtc/landmarks/{seq}/cert-details

Return parsed details of a landmark certificate as JSON.

Query parameters: ca_id (optional).

Requires the administrator, ca_operations, or auditor role.

Response 200 OK:

{
  "sequence_no": 3,
  "cert_text": "Subject:       CN=…\nIssuer:        …\n…"
}

Returns 503 Service Unavailable when the landmark certificate has not been built.

GET /admin/mtc/inclusion-proof/{cert_id}

Return the inclusion proof for a certificate identified by UUID.

Requires the administrator, ca_operations, or auditor role.

Response 200 OK:

{
  "leaf_index": 10,
  "tree_size": 42,
  "proof": [
    { "hash": "a1b2c3…" },
    { "hash": "d4e5f6…" }
  ]
}

Returns 404 Not Found when the certificate does not exist or has no MTC log index.

GET /admin/mtc/standalone/{cert_id}

Download the standalone MTC certificate DER for a certificate identified by UUID.

Requires the administrator or ca_operations role.

Response 200 OK: Content-Type: application/octet-stream (raw DER bytes).

Returns 404 Not Found when the certificate or its standalone DER does not exist.

GET /admin/mtc/consistency-proof

Return root hashes for consistency verification between two tree sizes.

Query parameters:

ParameterRequiredDescription
fromYesOlder tree size (must be positive and less than to).
toYesNewer tree size (must not exceed current tree size).
ca_idNoCA identifier.

Requires the administrator, ca_operations, or auditor role.

Response 200 OK:

{
  "from_size": 5,
  "to_size": 10,
  "from_root": "a1b2c3…",
  "to_root": "d4e5f6…"
}

Error responses:

StatusCondition
400 Bad Requestfrom or to is zero, from >= to, or to exceeds the current tree size.

GET /admin/mtc/subtree-root

Compute the subtree root hash over a leaf range.

Query parameters:

ParameterRequiredDescription
startYesStart index (inclusive). Must be aligned to the next power of two of the range size.
endYesEnd index (exclusive). Must not exceed the current tree size.
ca_idNoCA identifier.

Requires the administrator, ca_operations, or auditor role.

Response 200 OK:

{
  "start": 0,
  "end": 10,
  "root_hash": "a1b2c3…"
}

Error responses:

StatusCondition
400 Bad Requeststart >= end, start is not aligned, or end exceeds the tree size.

GET /admin/mtc/revoked-ranges

Return revoked leaf-index ranges.

Query parameters: ca_id (optional).

Requires the administrator, ca_operations, or auditor role.

Response 200 OK:

{
  "revoked_ranges": [
    { "start": 5, "end": 8 }
  ],
  "total": 1
}

GET /admin/mtc/checkpoint

Return the C2SP tlog operator checkpoint as signed-note text.

Query parameters: ca_id (optional).

Requires the administrator, ca_operations, or auditor role.

Response 200 OK: Content-Type: text/plain; charset=utf-8.

Returns 503 Service Unavailable when no MTC signing key is configured.

GET /admin/mtc/cosignature

Return the C2SP tlog cosignature checkpoint text.

Query parameters: ca_id (optional).

Requires the administrator, ca_operations, or auditor role.

Response 200 OK: Content-Type: text/plain; charset=utf-8.

Returns 503 Service Unavailable when no MTC signing key is configured.

POST /admin/ca/{id}/mtc/force-checkpoint

Force an immediate MTC checkpoint for the specified CA. Triggers checkpoint production, cosignature gathering, and standalone certificate construction. An admin.action audit event with {"action": "mtc.force-checkpoint"} is recorded on success.

Requires the administrator or ca_operations role.

Response: 204 No Content on success. Returns 404 Not Found when the CA ID does not exist or MTC is not enabled.

POST /admin/ca/{id}/mtc/force-landmark

Force an immediate MTC landmark allocation for the specified CA. Allocates a new landmark at the current tree size and produces the landmark certificate. An admin.action audit event with {"action": "mtc.force-landmark"} is recorded on success.

Requires the administrator or ca_operations role.

Response: 204 No Content on success. Returns 404 Not Found when the CA ID does not exist or MTC is not enabled.

Delegation management endpoints

These endpoints are active only when server.delegation_enabled = true is set in the configuration. Delegations represent pre-configured RFC 9115 IdO-to-NDC delegation policies: a CSR template and an optional CNAME map. Read operations (GET) are available to all authenticated roles; write operations (POST, PUT, DELETE) require the administrator or ca_operations role.

GET /admin/delegations

List all delegation objects. Optionally filter by account.

Query parameters:

ParameterDescription
account_idFilter by ACME account UUID.
limit1–1000, default 100.
offsetDefault 0.

Response 200 OK:

{
  "delegations": [
    {
      "id": "b1c2d3e4-…",
      "account_id": "d290f1ee-…",
      "csr_template": "{…}",
      "cname_map": null,
      "created": 1746154800,
      "updated": 1746154800
    }
  ],
  "limit": 100,
  "offset": 0
}

POST /admin/delegations

Create a new delegation object. The csr_template field is validated against the RFC 9115 §4 schema at write time; a malformed template is rejected with 400 Bad Request.

Request body:

{
  "account_id": "d290f1ee-…",
  "csr_template": {
    "keyTypes": [{"type": "EC", "curve": "P-256"}],
    "subject": {"commonName": {}, "organization": "ExampleCorp"},
    "extensions": {
      "subjectAltName": {},
      "keyUsage": ["digitalSignature"],
      "extendedKeyUsage": ["1.3.6.1.5.5.7.3.1"]
    }
  },
  "cname_map": null
}

cname_map is optional. When present it is a JSON object mapping source FQDNs to target FQDNs (e.g. {"cdn.example.com": "cdn.provider.example"}).

Response 201 Created:

{ "id": "b1c2d3e4-…", "created": 1746154800 }

GET /admin/delegations/{id}

Fetch a single delegation object by UUID.

Response 200 OK:

{
  "id": "b1c2d3e4-…",
  "account_id": "d290f1ee-…",
  "csr_template": "{…}",
  "cname_map": null,
  "created": 1746154800,
  "updated": 1746154800
}

Returns 404 Not Found when the ID does not exist.

PUT /admin/delegations/{id}

Replace the csr_template and/or cname_map of an existing delegation. The csr_template is re-validated at write time. Only csr_template and cname_map may be updated; account_id is immutable.

Request body:

{
  "csr_template": {…},
  "cname_map": {"cdn.example.com": "cdn.provider.example"}
}

Response: 204 No Content on success, 404 Not Found when the ID does not exist.

DELETE /admin/delegations/{id}

Delete a delegation object. Returns 409 Conflict when one or more orders still reference this delegation (the orders must be finalized or deleted first).

Response: 204 No Content, 404 Not Found when the ID does not exist, 409 Conflict when orders reference it.

Audit events

Every write operation emits a structured audit event to the configured audit backend (systemd journal namespace, JSONL file, or in-process store):

OperationEvent type
POST /admin/delegationsdelegation.create
PUT /admin/delegations/{id}delegation.update
DELETE /admin/delegations/{id}delegation.delete

Query these events with GET /admin/audit?type=delegation.create or akamuctl audit --type delegation.create.

CLI

All delegation management endpoints are wrapped by akamuctl delegation. See akamuctl — Admin CLI for the full command reference including flags and examples.

Audit trail

Every admin operation is written to a structured audit backend. Three backends are available:

  1. Systemd journal namespace (default) — when running under systemd with LogNamespace=akamu (see contrib/systemd/akamu.service), events are stored in /var/log/journal/<machine-id>.akamu/.
  2. JSONL file — when [server].audit_log_file is set, events are written as append-only JSON Lines to the specified file. External logrotate(8) with copytruncate is expected for rotation. Each query scans at most 500,000 lines to prevent unbounded reads on unrotated files.
  3. In-process store — in tests or development without systemd and without a configured file, an in-memory store is used automatically.

Each journal entry carries structured fields:

Journal fieldContent
AKAMU_EVENT_TYPEEvent type string (e.g. cert.issue, admin.login)
AKAMU_SUBJECTResource identifier (account UUID, certificate serial, etc.)
AKAMU_PRINCIPALAuthenticated operator name or acme:<jwk_thumbprint>
AKAMU_OUTCOMEsuccess or failure
AKAMU_DETAILJSON object with operation-specific fields

Query examples:

journalctl --namespace=akamu                              # all audit events
journalctl --namespace=akamu AKAMU_EVENT_TYPE=cert.issue   # by type
journalctl --namespace=akamu AKAMU_OUTCOME=failure         # failures only

Retention is managed by journald itself (see contrib/systemd/journald@akamu.conf for default settings: 500 MB disk, 1 year max age).

Overflow policy (FAU_STG.4)

When audit_max_events is set (backward-compatible alias: audit_max_rows), the server tracks an in-memory event count since startup. The audit_overflow policy determines what happens when the count reaches the limit. The default is "drop_oldest", which is effectively a no-op (journald or the file backend manages its own retention). The alternative "halt" refuses all new requests until the server is restarted.

Alarm response (FAU_ARP.1)

The server maintains an in-memory rolling 5-minute count of security.violation audit events. When the count reaches audit_alarm_threshold (default 10), the audit_alarm_action fires:

  • "syslog" (default) — a CRIT-level message is emitted via tracing, which is forwarded to the system log by the process manager.
  • "halt" — the server stops accepting new requests until restarted.

The halt flag is also set when the "halt" overflow policy is triggered.

Operator management workflow

Initial setup

Akāmu auto-provisions the first administrator on first run. Enable TLS on the main listener via [tls] and configure [tls.client_auth] so operator client certificates are verified. Then add [admin] keys that point to where the bootstrap certificate and key should live:

[tls]
enabled   = true
cert_file = "/etc/akamu/server.crt"
key_file  = "/etc/akamu/server.key"

[tls.client_auth]
ca_files = ["/etc/akamu/operator-ca.pem"]
required = false   # allow GSSAPI-only clients that carry no cert

[admin]
# Bootstrap operator — generated automatically on first run.
bootstrap_operator_cert_file = "/etc/akamu/admin-bootstrap.pem"
bootstrap_operator_key_file  = "/etc/akamu/admin-bootstrap-key.pem"
# bootstrap_operator_name    = "admin"   # default (bare name → directoryName SAN)
# bootstrap_operator_name    = "dns:admin.example.com"  # dNSName SAN
# bootstrap_key_type         = "ec:P-256"  # default

On the first startup, if both bootstrap files are absent and the operators table is empty, Akāmu:

  1. Generates a fresh private key (using bootstrap_key_type).
  2. Issues a client certificate signed by the Akāmu CA with CN=<bootstrap_operator_name> and a SubjectAltName extension. The SAN type is selected by an optional prefix on the name (dns:, email:, ip:, uri:, dn:); a bare name defaults to a directoryName SAN built from the Subject DN. See bootstrap_operator_name for the full prefix table.
  3. Writes the key and certificate PEM files to the configured paths.
  4. Registers the certificate’s SHA-256 fingerprint in the operators table with the administrator role.

After first boot, use the bootstrap cert to authenticate and provision real operator accounts:

# Add a permanent operator with their own client cert.
akamuctl --cert /etc/akamu/admin-bootstrap.pem \
         --key  /etc/akamu/admin-bootstrap-key.pem \
    operator add --name alice --role administrator \
                 --cert-file /etc/akamu/alice-client.pem

# Deactivate the bootstrap operator once a permanent one is in place.
akamuctl --cert /etc/akamu/admin-bootstrap.pem \
         --key  /etc/akamu/admin-bootstrap-key.pem \
    operator remove 1

Note: If the bootstrap cert/key files are absent but the operators table already contains rows (e.g. after a mistaken file deletion), Akāmu refuses to start with an error rather than silently creating a duplicate administrator. Restore the files from backup, or remove bootstrap_operator_cert_file and bootstrap_operator_key_file from the config and manage operators entirely through akamuctl.

Revoking access

Deactivate an operator with akamuctl operator remove <id> or PATCH /admin/operators/{id} with {"active":false}. The record is preserved for audit trail continuity. The operator’s active sessions are invalidated immediately and they cannot authenticate again until reactivated.


Troubleshooting

Error message or symptomLikely causeFix
admin API is not configured (HTTP 404)The [admin] section is absent from config.toml.Add an [admin] section with bootstrap_operator_cert_file and bootstrap_operator_key_file to enable the admin API.
Authentication required: Bearer token, mTLS certificate, or Negotiate (HTTP 401)No valid credential was presented in the request.Supply a client certificate via mTLS, a session token via Authorization: Bearer <token>, or a Kerberos token via Authorization: Negotiate <token>.
Authentication required: Bearer token or mTLS client certificate (HTTP 401)No credential was presented, and GSSAPI is not configured for the admin interface.Use mTLS or a Bearer session token. To enable Kerberos, configure [admin.gssapi].
client certificate not recognized (HTTP 403)The mTLS client certificate’s SHA-256 fingerprint does not match any registered operator.Verify the certificate fingerprint with openssl x509 -in cert.pem -noout -fingerprint -sha256. Register the operator with akamuctl operator add --cert-file cert.pem.
session token expired or invalid; please re-authenticate (HTTP 401)The Bearer token has exceeded the idle timeout (session_ttl_secs, default 1 hour) or has been invalidated.Obtain a fresh session token via POST /admin/session.
session locked due to inactivity; re-authenticate (HTTP 423)The session exceeded session_lock_secs of inactivity but has not yet fully expired.Re-authenticate via POST /admin/session to obtain a new token.
operator account locked due to repeated authentication failures (HTTP 403)The operator exceeded max_failed_auth failed login attempts.An administrator must unlock the account via POST /admin/operators/{id}/unlock or akamuctl operator unlock <id>.
insufficient role for this operation (HTTP 403)The authenticated operator’s role does not permit this endpoint.Check Operator Roles for the required role. Use an operator with the appropriate role (administrator, ca_operations, ca_ra, or auditor).
ca_ra operator has no CA scope configured (HTTP 403)A ca_ra operator attempted an operation but has no ca_id scope assigned.Assign a CA scope via PATCH /admin/operators/{id} with {"ca_id": "<ca-id>"}.
authentication rate limit exceeded; try again later (HTTP 429)Too many authentication attempts from the same IP address within the rolling 5-minute window.Wait a few minutes before retrying. Investigate whether a misconfigured client is retrying in a loop.
Kerberos principal is not a registered operator (HTTP 403)GSSAPI authentication succeeded, but the Kerberos principal is not registered in the operators table.Register the principal as an operator: akamuctl operator add --name <name> --role <role> --gssapi-principal <principal@REALM>.
GSSAPI not configured for admin interface (HTTP 401)A Negotiate token was sent, but [admin.gssapi] is not configured.Add an [admin.gssapi] section with a keytab or gssproxy configuration to enable Kerberos for the admin API.
server halted: audit overflow or security alarm (HTTP 503)The audit overflow or security alarm policy is set to "halt" and the threshold was reached.Restart the server after investigating the audit events. Consider switching the policy to "drop_oldest" or "syslog".

See also