API keys
Creating a key, setting an expiry, disabling it, what the platform stores, and the difference between disabled, expired and deleted.
Creating one
| Field | Notes |
|---|---|
| Description | Optional, and worth writing. It is how you tell two keys apart later. |
| Expiry date | Optional. 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:
| Action | The key as a header | Its OAuth clients | Reversible |
|---|---|---|---|
| Disabled | Stops | Keep working | Yes |
| Expired | Stops | Stop | No |
| Deleted | Stops | Deleted, with their tokens | No |
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:
| Stored | For |
|---|---|
| A hash salted with your tenant | Verifying a presented key |
| An unsalted hash | Finding the row without being told which tenant |
| The last few characters | Showing 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 tokensWhat 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.