Manage your end-users
Find a user, read what the directory holds, and act — invite, ban, revoke, reset, erase.
The management App is where you and your support team work with the directory: find an end-user, read what Lessly Users knows about them, and act — invite, ban, revoke a session, reset a lost second factor, revoke a passkey, or erase an account. Everything here is scoped to one product.
Lessly Users is the Users entry in the product’s navigation, and it has seven screens:
| Screen | What it is |
|---|---|
| Users → Overview | The product’s sign-in numbers |
| Users → Users | The directory |
| Users → Keys | The API keys and the JWT signing keys — see Configure authentication |
| Users → Webhooks | The endpoints and their delivery logs — see Receive user events |
| Users → Waitlist | The pre-launch queue — see Run a waitlist |
| Users → Import | The import wizard — see Migrate users in and out |
| Users → Configuration | The authentication configuration |
Two screens sit a level down: Users → Users → <user>, a single end-user’s record, and Users → Webhooks → Deliveries, one endpoint’s delivery log.
That is the whole of the App, and nearly every operation on this page has a screen on it. The few that do not — editing a metadata bag, reading a single session by its id, reading the operator audit across the product, and the retention and service-level readings — are called out where they are described. Those are performed through the product’s API, and each is available to an agent as well, so a support workflow can be automated even where nothing in the App performs it. The generated REST reference at /reference/openapi/users is the source of truth for the paths and fields involved.
Every operation that does have a screen is available to an agent too, so a workflow can be automated once you have done it by hand and know what it does.
Find a user
Users → Users opens the directory: a list of your end-users, most recently created first, walked a page at a time. Its search box matches an email address — whole or partial, in the form the user typed or in its normalized form — and the user’s name. It does not match the stable user id: to land on a record from an id you hold in your own logs or in a token, read that user directly with users_users_get, or go to its address in the App. A row opens Users → Users → <user>.
Above the list sits one row of filters, and it filters on lifecycle status only: All, Active, Banned and Waitlisted. The screen offers no other filter — not by sign-in method, not by whether an address has been verified, not by a date range. Search and status are what it narrows by, and a row’s badge is where a deleted or erased record announces itself. Soft-deleted users are hidden from the list, and the App offers no control that shows them; users_users_list has includeDeleted for that.
People who signed up to a waitlist are also directory records, and they are kept out of the default list so they do not drown the users who can actually sign in. Pick Waitlisted to see them, or work with them in the funnel described in Run a waitlist.
Read a record
Users → Users → <user> is the record. It is laid out as four sections — Identifiers, Sessions, MFA factors and Audit trail — under a header that carries the user’s status and the operator actions.
Identity. The stable opaque id — the one you store in your own database and the one that appears in every access token and event — plus the name and image if your product collects them.
Identifiers. The Identifiers section lists the email addresses that reach this person, each marked verified or unverified and one of them marked primary. A user may hold several. Each address carries its own verification state, so a record can hold a verified address and an unverified one at the same time. “Is this user verified?” is always a question about an address. The section is a read-out: nothing on it adds or removes an address. Moving the primary address has its own screen, described under Edit a record.
Sign-in methods. Separately from the addresses, the ways this person can prove who they are: a password, a linked Google or GitHub account. A user always keeps at least one usable way in — the last one cannot be removed. The record has no section for these, and no screen unlinks a provider or clears a password; that is done through the API.
Sessions. The Sessions section holds one row per signed-in device, naming it by the user agent and the address it connected from, with when it last refreshed and when it expires. Ended sessions stay in the list, marked as such. This is the list an end-user sees as “your devices” in your own product, and it is the list you revoke from.
Factors. The MFA factors section lists the enrolled multi-factor methods: an authenticator app once its enrolment has been confirmed, and how many single-use backup codes remain. The same section lists the user’s passkeys, one row per credential, with the name the user gave it, how it is reached and when it was last used. A passkey counts as a verified factor, so an account holding one has two-step verification whether or not it has an authenticator app.
Profile and metadata. Three bags of your own data hang off the record, and they differ in who may write them and who may read them:
| Bag | Written by | Read by | Notes |
|---|---|---|---|
| Public metadata | your backend | your backend and the end-user | A size-capped projection of it travels in the access token |
| Private metadata | your backend | your backend | Never leaves your side |
| Unsafe metadata | the end-user | everyone | Never base an authorization decision on it |
Use public metadata for anything your backend wants without a lookup — a plan name, a role, a feature flag. Because it rides in the token, it is capped, and an over-large write is refused rather than silently breaking that user’s next sign-in.
Security history. The record’s own audit trail, shown in its Audit trail section and described at the end of this page.
Create and invite a user
Invite user on Users → Users opens a dialog that takes an email address and, optionally, a display name, and invites that person. No password is set this way; the user receives an invitation email and chooses their first credential from it, which is also how invite-only sign-up works — see Configure authentication.
Creating an account outright — a record written straight away, with no email sent and nobody asked to do anything — is a different operation and a different button. Create user on the same screen takes an email address and an optional display name, and offers one checkbox: record the address as already verified. Nothing checks it, so tick it only for addresses you already trust. Metadata cannot be set from either dialog — users_users_create and users_users_invite take publicMetadata and privateMetadata, so put it on through the tool instead. Inviting is idempotent by address: re-inviting returns the same user rather than creating a second.
An invitation is single-use, expires, and can be revoked before it is accepted; revoking it leaves the record in place so you can send a fresh one. Re-inviting the same address returns the same user rather than creating a second, so pressing the button twice cannot make two records. Accepting it runs the ordinary sign-up flow, so everything your product’s configuration requires — a password that meets the policy, a second factor if you demand one — still applies.
Edit a record
Metadata and profile take effect immediately once written. A change to public metadata reaches your backend on the user’s next access token, within the token lifetime. The profile has a screen — the Profile section of the record, above — and the three metadata bags do not; those are written through the product’s API, which is available to an agent.
The primary identifier can be switched to any other address on the record. Set primary email on the record opens a dialog that takes the new address and a written reason — what identity proofing you did, under which ticket — and will not submit without one, because the reason is recorded verbatim against your name. What it submits is a request, not a field edit: like the operator email-change override in Run a sign-in flow it takes effect after a delay — the operator-override delay in this product’s emailChange settings — and both addresses are notified in the meantime so a mistaken or malicious change can be stopped. The primary address is what Lessly Users uses to reach the person and what your backend receives in the token’s email claim, and only a verified address may become primary.
Adding or removing an address is subject to the rule that keeps a user reachable: the last verified address that a person can receive a sign-in code at is not removable while it is their only way in.
Reset a lost second factor
When a user loses both their authenticator app and their backup codes, an operator resets their factors with Reset MFA on the record’s MFA factors section. The reset clears the enrolled factors, revokes all of the user’s sessions, and lets them enrol again on their next sign-in.
Revoke a passkey
Revoke on a passkey row of the record’s MFA factors section removes that one credential. The dialog will not submit without a written reason — what identity proofing you did, under which ticket — which is recorded verbatim against your name in the audit trail. The same revoke is available through the product’s API, and to an agent.
Unlike the factor reset it takes effect immediately: a lost or stolen device is exactly the case where waiting three days is the wrong answer, and removing one credential leaves the account’s other ways in untouched, so there is nothing for the user to abort.
It is refused when that passkey is the account’s last way to sign in. That refusal is decided against the whole record — every identifier and every other credential — and no operator permission overrides it; the user adds another way in first.
Revoke a session
The record’s Sessions section is where sessions are ended. Revoke on a row ends that one session — that device signs out and nothing else changes — and Revoke all on the section header ends every session the user has, after a confirmation. A row that has already ended offers no button. A revoked session’s refresh stops working immediately.
Reading a single session by its id, rather than the user’s whole list, has no screen; it is done through the product’s API. The section already carries every field that call returns, so what it is for is the jump the other way — you have a session id in one of your own log lines and want the session behind it without knowing whose it is.
Because your backend verifies access tokens locally, a revoked session’s current access token remains valid for the rest of its lifetime, ten minutes by default. Where that matters — a stolen laptop, a fraud case — the server library can check a token against the live session on the calls that matter. Sessions and tokens covers both modes and Configure authentication covers shortening the token lifetime.
Ban and unban
A user is banned in either of two places: the Ban action on their row in Users → Users, or the Ban button in the header of their record. Both ask for confirmation. Where a user is already banned, the same two places offer Unban instead, and it takes effect without a confirmation step.
Banning refuses every future sign-in and revokes all existing sessions at once. The record, its identifiers and its metadata stay exactly as they were, so a ban is reversible: unbanning restores the account, and the person signs in again with the credentials they already had. Use a ban for abuse and for accounts you may want back; use deletion for accounts that are leaving.
Impersonate a user for support
To reproduce what a user is seeing, you can open a session as them. Sign in as this user in the header of Users → Users → <user> starts one, behind a dialog that states the thirty-minute ceiling and will not submit without a written reason. The button is offered only once the product has at least one redirect URI allowed, because an impersonated session has nowhere legal to land otherwise.
What you get back is a token to copy, shown on the screen and held nowhere else — not in a link, not in an address, not written back into anything the page caches. The App performs no redirect and hands the session to nothing: you take the token to your own product yourself. That is deliberate, and it is what keeps an operator session from being one click away from anyone who reaches the screen. It is the strongest operation Lessly Users offers, and it is fenced accordingly:
- it requires the dedicated permission, not merely access to the directory;
- the session is capped at thirty minutes and cannot be refreshed;
- nothing about the account can be changed while you are inside it — no password, no factors, no addresses, no session revocations;
- your backend can tell: the session is marked as an impersonation and the server library exposes it as such, so you can hide sensitive screens or refuse destructive actions during one;
- it is recorded with your name as the actor, your reason is recorded verbatim, and — unless you turn the notice off for your product — the user is emailed that it happened, with your reason in the email.
Delete or erase an account
Two different operations, for two different reasons. Both have a screen: Delete and Erase in the header of Users → Users → <user> each act on that user behind a confirmation that spells out what will be gone.
| Delete | Erase | |
|---|---|---|
| What it is for | Retiring an account. | A data-protection request. |
| Sessions | Revoked at once. | Revoked at once. |
| The record | Kept for a grace period, so a mistaken deletion can be undone and your own systems can reconcile. | Purged: addresses, profile, metadata and credentials are gone; a tombstone stands in for the identity. |
| The email address | Released for re-use after the grace period. | Free again. |
| Undo | Yes, during the grace period. | None. Erasure is final. |
| Security-log entries | Kept. | Kept, with the identity in them pseudonymized. |
Before either, you can export. Export on Users → Users writes a CSV of exactly the view on screen — whatever you searched for and filtered to — walking the whole result set rather than the page you can see, and reporting the running row count while it goes. An export is itself recorded; Migrate users in and out describes the format and the export of password hashes.
What erasure means for the data itself, and how it fits the platform’s retention and deletion model, is on Data and privacy.
Import users in bulk
Bringing an existing user base with you is a first-class operation in the App, not a support ticket. Users → Import is the wizard, and Migrate users in and out covers it in full. You paste rows or choose a file — the email address, a name, and optionally the password hash you hold today — and say which column is which. Before anything is sent, the wizard reports how many rows it will import and how many it rejected on the spot for being unreadable. A row that collides with an address already in the directory is not caught there; it comes back as a rejection in the report the import writes afterwards.
What happens on the run:
- each accepted row becomes a directory record with its address in the verification state you declared;
- a supplied password hash is stored as it is, and the password keeps working — it is verified in its original format the first time that person signs in, then transparently re-stored in argon2id;
- a hash that cannot be used marks the account as needing a password reset, so the user is asked to set a new password rather than being locked out;
- rows with no credential at all become invitable accounts, which you then invite from the App as above.
The whole import is recorded in the security history.
Read the audit trail
Two records are kept, for two audiences.
The user’s security history is the Audit trail section of Users → Users → <user>, and it is the thing you read during a support conversation. Each entry names the action, when it happened and who the actor was, and a Refresh button re-reads it without leaving the record. It appends an entry for each sign-in, successful or failed, each new device, each change of password, address or factor, each session revocation, each ban, each impersonation with the operator who did it, each import that touched the account and each erasure. Entries are append-only: nothing in the App edits or removes one.
The operator audit is the mirror image — the same events organized by who did them rather than by whom they were done to. It answers “what did this member of my team do last Tuesday”, covers actions taken by an agent as well as by a person in the App, and is the record you reach for during an incident or an audit. The App shows the trail one user at a time and no more: there is no screen that lists it across the product or narrows it to an actor. Reading it that way is done through the product’s API, and is available to an agent.
Every entry carries the actor, the action, what it targeted, the address and browser it came from, and when it happened. Note what the filters do not offer: users_audit_list narrows by action, by target and by time range, and there is no way to ask it what one member of your team did — an actor is something you read off an entry, not something you can filter for. Together with the notification emails Lessly Users sends on its own — a changed password, a new device, a requested email change with its revert link, a pending MFA reset — this is what makes an operator override safe to offer: nothing sensitive happens quietly, either to your team or to your end-user.
Read the numbers
Users → Overview is where the product’s sign-in stands, in five blocks: Users (total, active, waitlisted, banned, deleted and erased), Sessions, Second factors, Sign-ins (succeeded, failed and the success rate) and Abuse signals (lockouts, MFA lockouts, failed sign-ins, breached passwords seen and refresh-token reuse detected).
Read the window selector — 24 hours, 7 days, 30 days or 90 days — carefully, because it does not move everything. The user, live-session and factor populations are counted as of right now and ignore it entirely; the sign-in counters, the sessions established, and the whole abuse block are computed over the window. Each number carries its scope on screen, so nothing has to be inferred. A success rate reads “No attempts” rather than 0% when nothing was tried, because an idle window and a broken one are opposite facts.
Two read-only operations still have no screen anywhere in the App, and it is worth knowing they are there:
- Retention settings — how long Lessly Users keeps what it keeps.
- Service-level readings — the service level it is meeting for your product.
Both are read through the product’s API and are available to an agent.
Next steps
- Configure authentication: the sign-in methods, password policy and session lifetimes the operations above obey.
- Read the token contract: why a revoked session can outlive its revocation by one token lifetime, and how to close that gap.
- Migrate users in and out: the import file format, and getting your users back out.
- Run a waitlist: the directory before your product is open to everyone.