Ensemble Docs

API keys

Creating a key, setting an expiry, disabling it, what the platform stores, and the difference between disabled, expired and deleted.

Creating one

FieldNotes
DescriptionOptional, and worth writing. It is how you tell two keys apart later.
Expiry dateOptional. Tomorrow to ten years out. Cannot be changed afterwards.

The key is shown once. Copy it then; afterwards the platform holds only hashes of it and the last few characters, so nobody, including an administrator, can recover it.

curl https://service.ensembleapp.dev/external/v1/benefits/coverage/E-4417 \
  -H 'x-api-key: sk_...'

The tenant comes from the key, so no tenant header is needed. Keys issued before keys could be found on their own still require x-tenant-id; rotating such a key removes the need.

Expiry

An expiry is set at creation and is then fixed. The form offers tomorrow to ten years out, and the chosen day is honoured in full: "expires on the 31st" means the key works through the 31st in your timezone, not from midnight at the start of it.

Expired keys are hidden from the list behind a Show expired toggle. They are kept rather than deleted, because an expired key is still the record of a credential that existed and is the thing to tidy up later, but it cannot be revived.

The expiry cannot be edited on purpose. An expiry that can be pushed back is not a commitment, and a key that needs a different date is a different key. Create the replacement, grant it, move the caller, then delete the old one.

Disabling

Disabling switches the key off as an x-api-key header and nothing else. It is reversible and needs no reconfiguration to undo.

What it does not do is stop the OAuth clients built on that key. They keep working, which is deliberate: a caller who would rather not hold a long-lived key can be given a client, and the key itself switched off. See OAuth clients.

Disabled, expired, deleted

The three are not variations of the same thing, and the difference is the most important table on this page:

ActionThe key as a headerIts OAuth clientsReversible
DisabledStopsKeep workingYes
ExpiredStopsStopNo
DeletedStopsDeleted, with their tokensNo

So to stop a client, disable or revoke the client. To stop everything an identity can do at once, delete the key or let it expire.

What is stored

Not the key. Three things derived from it:

StoredFor
A hash salted with your tenantVerifying a presented key
An unsalted hashFinding the row without being told which tenant
The last few charactersShowing you which key a row is, in a list or a log

The salted hash is what makes a stolen key useless to someone who does not know which tenant it belongs to, and it is also why the salted hash cannot double as the lookup. The unsalted one finds the row; the salted one still verifies it, with the tenant taken from that row, so a lookup bug cannot authenticate a key against the wrong tenant.

Logs and API responses carry the key's last characters, never the key, and grants are keyed on a hash. The hash is not usable to authenticate, but it is still key-derived, which is why grant endpoints take it in a request body rather than in a URL where access logs would keep it.

Use is recorded, loosely

A key records when it was last used, throttled so that a busy key does not write on every call. It is for retiring keys, not for auditing them: a key with no recent use is a candidate for deletion, and a precise timestamp is not what this is for.

The API

GET    /api/tenants/api-keys                        List this tenant's keys
POST   /api/tenants/api-keys                        Create one. The plain key is in the response, once
PATCH  /api/tenants/api-keys/{hashedKey}/disable    Switch it off as a header
PATCH  /api/tenants/api-keys/{hashedKey}/enable     Switch it back on
DELETE /api/tenants/api-keys/{hashedKey}            Delete it, its clients and their tokens

What a key may call is granted per API endpoint, on that API's own page rather than here. The credentials screen shows the result, so a key's reach is readable in one place without being editable in two. See API Endpoints.

On this page