Skip to content

Tenant API Keys

Tenant API keys are long-lived, programmatic credentials scoped to a single organisation, sent in the X-Api-Key header (see the authentication model). A key is read-only unless it is created as an operator key. A read-only key is the right choice for CI jobs, integrations, and service-to-service calls that read tenant and site data and have no interactive user to drive a browser login.

Every key is prefixed with csk_ (Consystence key). Manage keys from the CLI with csy api-key create <name> [--role member|admin] [--days <n>] (the value is shown once), csy api-key list and csy api-key revoke <keyId> [--reason <text>], or through the admin surface under /api/admin/api-keys. This page is the authoritative contract for their request and response shapes.

Every key has a class, set when it is created and returned as class:

ClassBound toLongest lifetimeWhat it can do
read-only (the default)Its tenant2 yearsReads tenant and site data. Every write is refused, whatever its role.
operatorIts tenant, one site and one person30 days, never renewedRuns that site’s device commands and acknowledges its alarms, acting for its person.

A read-only key reads tenant and site data and never changes anything. Its role, member or admin, only decides which REST reads it may make. A write by a read-only key is refused with 403 (code api-key-read-only), and so is a read no key may make, such as key management or tenant administration (code api-key-read-not-permitted). No REST endpoint writes a tag value.

An operator key acts for one person on one site. Only an organisation owner or admin, signed in with their organisation account, can create one: never a site-PIN sign-in, another API key, or platform authority alone. It is bound to:

  • one site of the organisation that accepts Consystence commands (a native site); on a site server, that server’s own site only;
  • one person, an active member of the organisation whose role may run plant commands.

It holds an operator’s authority on that site and nothing more: it runs the site’s device commands and acknowledges its alarms, and never administers, engineers, reconfigures or deletes anything. It does this work through the live console (csy ui), where it also reads its site; over REST it can acknowledge alarms on its own site, and a request that names another site is refused. Commands keep their usual preconditions and confirmation steps (see Alarms & Commands), and every use records both the key and the person.

An operator key lives at most 30 days and is never renewed: issue a new key instead. If its person stops being an active member who may command, or its site stops accepting commands, the key stops working. From the CLI: csy api-key create <name> --class operator --site <code> --on-behalf-of <email> [--days <n>] (1 to 30 days, default 7).

On a cloud cell, an organisation owner or admin (see roles) creates, lists and revokes keys. Org members cannot manage keys, and platform authority alone creates none.

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, including a site-PIN principal, lists, creates and revokes keys, but creates member keys only;
  • a Site admin lists and revokes keys but does not create them;
  • other site roles cannot manage keys.

When a caller has both an organisation role and a site role, the organisation role decides.

An API key never creates, lists or revokes keys, whatever its class or role. Authorisation is evaluated against the role of the caller, which is always a person signed in to the organisation or, on a site server, with a site PIN.

The target tenant is never taken from the request body. It is always the caller’s own organisation (on a site server, the organisation the server is enrolled to), 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. With no class, the key is read-only:

{
"role": "admin",
"name": "ci-historian-reader",
"expiresAt": "2027-02-18T00:00:00Z"
}
  • role — member or admin (see roles); it only decides which REST reads the key may make. Any other role, owner included, is refused with 400. A role above your own is refused with 403, and so is a caller with no organisation role, except that on a site server an Owner or Admin site role may create member keys.
  • name — a label that appears in the list view and in expiry-reminder emails.
  • expiresAt — required; subject to the expiry policy below.
  • class — optional: read-only (the default) or operator (see minting an operator key). A read-only key is bound to no site or person, so a read-only request that names either is refused with 400.

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",
"class": "read-only",
"siteCode": null,
"onBehalfOf": null,
"onBehalfOfDisplay": null
}

siteCode, onBehalfOf and onBehalfOfDisplay are null except on an operator key.

Name the class, the site and the person, and leave out role:

{
"class": "operator",
"name": "shift-console",
"siteCode": "dam-pump-station",
"onBehalfOf": "jane@example.com",
"expiresAt": "2026-02-25T09:14:00Z"
}
  • siteCode — the one site the key acts on: a site of your organisation that accepts Consystence commands (a native site). On a site server, only that server’s own site.
  • onBehalfOf — the person the key acts for: the email (or account id) of an active member whose role may run plant commands.
  • expiresAt — at most 30 days from now.

The 201 response has the shape shown above, with "class": "operator" and "role": "operator" (not an organisation role). siteCode is the site’s code, onBehalfOf the person’s account id, and onBehalfOfDisplay their label when the key was issued. A caller who may not create an operator key is refused with 403. A site or person that is not in your organisation returns 404; a site that does not accept commands, or a person whose role may not command, returns 409.

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.
  • Prefix — csk_ 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 creator's subject id…",
"createdAt": "2026-02-18T09:14:00Z",
"expiresAt": "2027-02-18T00:00:00Z",
"lastUsedAt": "2026-02-18T09:20:11Z",
"revealPendingUntil": null,
"class": "read-only",
"siteCode": null,
"onBehalfOf": null,
"onBehalfOfDisplay": null
}
]

createdByUserId is the subject identifier of the person who created the key; for an organisation sign-in, that is the per-application OIDC sub. lastUsedAt is updated best-effort as the key authenticates requests, and is null until first use. revealPendingUntil is null for a key created through this endpoint. A key created in the web console carries it until the console confirms the key’s one-time display; until then the key cannot authenticate, and if that time passes unconfirmed it never will, so revoke it. class, siteCode, onBehalfOf and onBehalfOfDisplay are as in the mint response.

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: when an organisation is removed, its keys stop working.

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

BoundRead-only keyOperator key
Minimum1 hour from now1 hour from now
Maximum2 years (730 days) from now30 days from now

Requests outside this range are rejected with 400 (ExpiryPolicyViolation for a read-only key); a value in the past fails the minimum bound. The web console and csy api-key create propose 90 days for a read-only key and 7 days for an operator key.

A background reminder service polls every 24 hours and emails the creator of a read-only key when it falls 24–30 days from expiry, with a 7-day cooldown so a key is reminded at most once per window. An operator key gets no reminder: issue a new one when it expires. 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 server recognises the header and validates the key:

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 — and, for an operator key, only while its site and person still qualify. A request the key’s class does not allow is refused with 403 and a JSON body whose code names the reason. For how the platform chooses between this and the CIAM Bearer and site-PIN schemes, see the authentication model. The schemas on this page are the contract for the key-management endpoints.