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.
What to decide before opening the console
Section titled “What to decide before opening the console”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.
Creating the account from the console
Section titled “Creating the account from the console”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 full key exists only at that instant
Section titled “The full key exists only at that instant”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.
Handing it to its holder
Section titled “Handing it to its holder”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_FILEandTLS_KEY_FILEare 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.
When the console is not reachable yet
Section titled “When the console is not reachable yet”On a fresh database, or an unfinished deployment, add-user.sh performs the same
creation from a shell:
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:
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.
Replacing a lost or compromised key
Section titled “Replacing a lost or compromised key”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.
Cutting off an account’s access
Section titled “Cutting off an account’s access”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 call | it is deleted |
| has at least one call logged | it 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.
Reactivating a deactivated account
Section titled “Reactivating a deactivated account”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.