Skip to content
Version 1.0.0

Giving an API key to a team

Create the account, generate the key and hand it over: the key is readable only at the moment it is displayed.

In the reference architecture, Lemniscate does not manage identities: each developer authenticates against your directory, and there is no key to hand over (security white paper V3, section 6.1). Local accounts exist only where no directory is available. This page covers consoles whose build profile includes a local account store, where each account authenticates with an API key. It describes creating the account, handing over the key, and the one moment when the key is readable.

The plan is required at creation time, both in the console and on the command line: the gateway returns 403 for an account without a plan. When no plan exists, the console replaces the creation form with a message pointing to the Plans tab. Choosing the cap is described in Capping a customer’s spend.

Users tab, New user section: a name and a plan. The Create user button stays disabled until both fields are filled in.

The server generates the key itself; the administrator does not supply one. The creation response returns it in clear text, once. The console displays it with a note that it will not be shown again.

The database does not store the key. It keeps a SHA-256 digest, which authentication looks up, and a displayable fragment of the form sk-lemniscate-…a3f9. The API Key column in the account list shows that fragment, and nothing else; no click reveals the full key.

The full key appears at three moments only: the response to a creation, the response to a rotation, and the response to issuing an additional key from an account’s Keys screen. No request, no screen and no database access retrieves it afterwards. The fragment is there to recognize a key, for example to confirm over the phone that you are talking about the same one; it is not there to recover it.

Copy the key at the moment it is displayed. A lost key cannot be recovered; it is replaced.

The product opens no transmission channel: it displays the key once. The repository describes no handover procedure, and this page does not invent one. The key travels by whatever means your organization uses for its secrets.

Two things to know before choosing that means:

  • The key accompanies every call from the workstation to the gateway. That trip is encrypted with TLS (security white paper V3, section 6.2): the gateway terminates TLS when TLS_CERT_FILE and TLS_KEY_FILE are set, and the startup log announces the selected mode. Do not hand over a key while that log announces a plaintext listener with no TLS termination in front of it.
  • In this profile, the console has a single shared set of credentials. No named accounts, no roles, no record of who created or handed over what. If you need to be able to say later who holds which key, that record is kept outside the product, at handover time.

On a fresh database, or an unfinished deployment, add-user.sh performs the same creation from a shell:

Fenêtre de terminal
export DATABASE_URL="postgresql://<utilisateur>:<mot-de-passe>@<hôte>:<port>/<base>"
cd gateway-db
./add-user.sh <nom-du-compte> <nom-du-plan>

The second argument is a plan name (plans.name), not an identifier: identifiers are SERIAL whose values differ between a database built from init.sql and a database built by the migration chain. If the plan does not exist, the script refuses, lists the available plans and writes nothing.

The script prints the key at the end of its run, with the same consequences as the console: the key is not stored, only its digest is.

To list the plans before choosing:

Fenêtre de terminal
psql "$DATABASE_URL" -c 'SELECT name, max_cost_euro FROM plans ORDER BY name'

This path is for bootstrapping. As soon as the console responds, it handles the accounts: it also rotates a key and removes an account, which the script does not.

Users tab, Rotate key button, behind a confirmation whose text announces the cutoff. The new key is displayed once, as at creation.

Every key the account held stops authenticating immediately; there is no period during which the old and the new one both work. Warn the holder before rotating.

The account keeps its identity: its identifier does not change, calls already logged under its name stay attributed to it, and its consumption for the month is not reset.

Users tab, Delete button, behind a confirmation. Two outcomes are possible, and the response says which one occurred:

The account…What happens
has never made a callit is deleted
has at least one call loggedit is deactivated (active = FALSE), the row remains

The distinction protects the usage history: deleting a row that request_logs references would destroy the attribution of calls already counted. A deactivated account no longer authenticates: the gateway only accepts active accounts, whatever their keys.

To block without deleting, a third action exists, and it is reversible: remove the account’s plan from the selector in the Plan column, value No plan. The Blocked badge appears, and the gateway returns 403.

No console button does this: the Active column is read-only, and the per-row actions are the keys screen, rotation and deletion. The route exists (PATCH /api/users/:id accepts active). The fix is done in SQL:

UPDATE users SET active = TRUE WHERE username = 'nom-du-compte';

This console gap is known.