Skip to content

Tenant API Keys

Tenant API keys are long-lived, programmatic credentials scoped to a single organisation. They are the credential behind the X-Api-Key authentication scheme of the MultiAuth selector — the right choice for CI jobs, integrations, and service-to-service calls that have no interactive user to drive a browser login.

Every key is prefixed with csk_ (Consystence key). Keys are minted, listed, and revoked through the admin surface under /api/admin/api-keys. These endpoints sit outside the CLI-scoped API reference — the CLI has no key-management commands — so this page is the authoritative contract for their request and response shapes.

On a cloud cell, all three endpoints sit behind the OrgAdmin authorisation policy. A caller may mint, list, or revoke keys only if they are:

  • an org owner or admin (see roles), or
  • a verified platform-admin.

Org members cannot create or revoke keys.

On a site server, key management — like site users and site configuration — is a site function, authorised on the caller’s site role: a principal with an owner or admin site role qualifies, including a site-PIN principal, while site members cannot manage keys.

Authorisation is evaluated against the role of the caller, which may itself be a CIAM Bearer session or another API key.

The target tenant is never taken from the request body. It is derived from the authenticated principal’s organisation — the org_slug claim (the org’s routing slug) is itself the tenant scoping key on the cell, while the account-minted org_ id (an 8-character Crockford base32 id, e.g. org_ab12cd34) serves only as the cross-plane correlation identity — so a caller cannot mint or touch keys outside its own organisation. Cross-tenant access attempts — and genuinely non-existent keys — return an identical, cause-agnostic 404, so the API never leaks whether a key exists in another tenant.

stateDiagram-v2
    [*] --> Active: POST /api/admin/api-keys<br/>(plaintext returned ONCE)
    Active --> Active: used on requests<br/>(X-Api-Key, last_used_at updated)
    Active --> Revoked: DELETE /api/admin/api-keys/{keyId}
    Active --> Expired: expires_at reached
    Revoked --> [*]
    Expired --> [*]

Minting a key — POST /api/admin/api-keys

Section titled “Minting a key — POST /api/admin/api-keys”

The request body supplies the key’s role, a human-readable label, and an expiry:

{
"role": "admin",
"name": "ci-historian-reader",
"expiresAt": "2027-02-18T00:00:00Z"
}
  • role — the org role the key will carry (see roles). Roles reserved for the account control plane are rejected with 400 ReservedRole; an org admin cannot self-mint a control-plane credential through this tenant-facing surface.
  • name — a label that appears in the list view and in expiry-reminder emails.
  • expiresAt — required; subject to the expiry policy below.

On success the endpoint returns 201 Created with the plaintext key in the response body — exactly once:

{
"keyId": "1f8e...-uuid",
"keyPrefix": "csk_AbCd",
"plaintext": "csk_AbCd…(32 base62 chars)",
"role": "admin",
"name": "ci-historian-reader",
"expiresAt": "2027-02-18T00:00:00Z",
"createdAt": "2026-02-18T09:14:00Z"
}

The plaintext is csk_ followed by 32 base62 characters (digits + upper + lower), drawn from a cryptographically secure RNG with rejection sampling to avoid modulo bias — roughly 190 bits of entropy.

What the platform actually stores is not the plaintext:

  • Verifier — what the platform stores is a one-way verifier of the key, never the plaintext. The plaintext cannot be recovered from storage or re-derived, and no later request re-emits it. A missing or misconfigured verifier secret is a hard start-up failure — the server refuses to run with broken hashing.
  • Prefixcsk_ plus the first 4 base62 characters of the plaintext (8 characters total, e.g. csk_AbCd). This is the only fragment retained for display; it is safe to show in lists, logs, and notifications.

Returns the tenant’s non-revoked keys, ordered by creation time (newest first). Each item is prefix-only metadata — never the plaintext, never the hash:

[
{
"keyId": "1f8e...-uuid",
"keyPrefix": "csk_AbCd",
"role": "admin",
"name": "ci-historian-reader",
"createdByUserId": "…the minting principal's subject id…",
"createdAt": "2026-02-18T09:14:00Z",
"expiresAt": "2027-02-18T00:00:00Z",
"lastUsedAt": "2026-02-18T09:20:11Z"
}
]

createdByUserId is the minting principal’s subject identifier: for a CIAM session that is the pairwise OIDC subnot the Entra oid, so it will not match directory object ids — and when a key is minted using another API key it is that key’s keyId. lastUsedAt is updated best-effort as the key authenticates requests, and is null until first use.

Revoking a key — DELETE /api/admin/api-keys/{keyId}

Section titled “Revoking a key — DELETE /api/admin/api-keys/{keyId}”

{keyId} is the key’s UUID (keyId from mint/list), not its prefix. The body is optional — supply a reason for the audit trail, or omit it:

{ "reason": "rotating credentials" }

A successful revocation returns 204 No Content. A key that does not exist, belongs to another tenant, or is already revoked returns the uniform 404 described under tenant scoping — revoked keys drop out of the tenant lookup, so a retried DELETE (say, after a timeout) sees 404, not a second 204; treat a 404 on retry as success, not as a wrong keyId. The audit revoked_at timestamp is set once and never advanced.

There is no separate rotation endpoint: rotate by minting a new key, switching the caller over, then revoking the old one. Keys also end with their organisation: deprovisioning an org removes its API keys along with the rest of the org runtime.

expiresAt is validated at the service layer against a horizon measured from now:

BoundValue
Minimum1 hour from now
Maximum2 years (730 days) from now

Requests outside this range are rejected with 400 ExpiryPolicyViolation. A value within range but in the past is likewise rejected by the minimum bound.

A background reminder service polls every 24 hours and emails the key’s creator when a key falls 24–30 days from expiry, with a 7-day cooldown so a key is reminded at most once per window. This is courtesy notification only — expiry itself is enforced at authentication time.

Present the plaintext on each request via the X-Api-Key header. The MultiAuth selector routes the request to the ApiKey scheme based on the header’s presence:

Terminal window
curl https://acme.consystence.com/api/... \
-H "X-Api-Key: csk_AbCd…"

The key authenticates only while it is active — not revoked and not past expiresAt. For how the platform chooses between this and the CIAM Bearer and site-PIN schemes, see the authentication model. The key-management endpoints themselves are not part of the CLI-scoped API reference — the schemas on this page are the contract.