Users
Platform → Users lists every account on the platform, across all organizations, and is the only place where accounts are created by an operator rather than by self-signup. The column layout and detail panel are summarised in Platform admin → Users; this page covers the operations.
Visible to users with is_staff. Two actions — Delete and Grant /
Revoke staff — additionally require is_superuser and are hidden
otherwise.
Find a user
- Search matches first name, last name, email, or username (case-insensitive substring); results refresh about 300 ms after you stop typing.
- Status filter: All / Active / Inactive (
is_active). - Role filter: All roles / Staff only.
The list is newest-signup first, 10 per page by default. There is no
org filter in the UI (the API accepts org=<id>); each row shows the org
count, with the org names on hover.
Create a user
+ Create User opens a modal with two tabs.
Single
First name, last name, email (required), phone, and a Password setup choice:
| Mode | What happens |
|---|---|
| Send setup link (user sets their own password) — default | The account is created with no usable password. A one-time set password link is emailed to the user (event invite) and shown to you in the modal afterwards, so you can copy and share it if email delivery is unreliable. |
| Set password now (admin-defined) | You type a password (minimum 8 characters); the user can sign in immediately. |
Username is not asked for — it is derived from the email's local part.
The new account is not added to any organization; do that from the
org's Members page or by invite link. Creation
is audit-logged (user.create, recording whether the password was
admin-set and whether the invite email went out).
Bulk
Type or paste email addresses (comma- or Enter-separated); they become chips. Create N users creates every address in invite mode — each gets an account with no password and a setup link emailed (best-effort). Names are left for the user to fill in when they set their password.
The results view lists every row with its outcome:
| Outcome | Meaning |
|---|---|
| Created, emailed / Created | Account made; the second form means the email could not be sent — the setup link is still in the API response |
| Already exists | Skipped — an account with that email exists |
| error detail | Missing email, duplicate within the batch, or a validation error |
Each created account is audit-logged as user.create with bulk: true.
Resend a setup link
An invited user who hasn't set a password yet shows Resend invite in
the row menu (driven by pending setup = the account has no usable
password — not by active/inactive). It regenerates the one-time link,
emails it when mail is configured, and confirms with a toast: Setup link
emailed to … or Setup link regenerated … (email not sent). Audit-logged
as user.resend_setup_link.
- Refused for accounts that have already activated — use a password-reset flow for those.
- Org Owners and Admins of an org the user belongs to can also trigger this (from their Members page); it is not staff-only.
Activate / deactivate
Edit → Account status → Deactivate / Activate toggles is_active. A
deactivated user can no longer sign in and is told by notification
(account deactivated); reactivation notifies too. You cannot deactivate
yourself. Audit-logged as user.deactivate / user.activate.
Deactivation is the reversible "disable"; it keeps the account, its memberships, and its work.
Verify email
Mark as verified in the detail panel flips the email-verified flag for users whose mail provider rejects the verify link — see Mark a user as verified.
Grant / revoke staff (superuser only)
Edit → Platform role → Grant staff / Revoke staff toggles is_staff.
The user is notified either way (you are now a platform administrator /
access removed). You cannot change your own flag here. Audit-logged as
user.staff_grant / user.staff_revoke.
Staff can administer any organisation (settings, members, quotas) but cannot open another org's project data unless they also hold the Full data access grant below.
Grant / revoke full data access (superuser only)
For a staff user, the detail panel shows Grant full data access /
Revoke full data access. When granted, that account may open the
projects, tasks, annotations and exports of any organisation — subject
to the platform policy at Settings → Policies → "Staff access to other
orgs' project data" (default Audited, which logs every such view).
Superusers always have it; regular staff never do unless granted. Give it
only to the few people who need it. Audit-logged as
user.data_access_grant / user.data_access_revoke. Full rationale:
Security model → Staff access to other organisations' data.
Delete (superuser only)
Delete in the row menu opens one modal that shows everything the deletion does and refuses to proceed until you have decided the fate of every organisation the person owns and typed their email address back. Nothing happens in stages: the whole plan is applied together when you press Delete user.
The modal lists, with counts:
- Removed with the account: the account itself, its sign-in tokens and passkeys, all organisation memberships, its contribution records (validated-work credits and payout lines) and its consent signatures.
- Kept, detached: the person's annotations stay in their projects with no author; projects they created stay with "created by" cleared; audit-log entries stay, showing the email as it was.
For each organisation the person owns you choose one of:
| Choice | Effect |
|---|---|
| Transfer ownership to a member you pick from the list | That member becomes Owner and is notified (org.transfer) |
| Make me the owner | You become Owner (a membership is added if needed) so you can transfer or delete it deliberately later |
| Delete the organisation | The organisation and everything in it go with the account (org.delete); the modal states how many projects and annotations that is |
The Delete user button stays disabled until every organisation has
a choice and the typed email matches. The API behaves the same way:
POST /api/platform/users/<id>/delete returns 400 without confirm
(the email) and a decision for every owned organisation, so nothing
can be left ownerless by any client. Preview data comes from
GET /api/platform/users/<id>/delete-preview.
Accessibility: the modal is a labelled dialog; focus moves into it on open and returns to the row on close; Tab stays inside; Escape closes; every control is a native form element with a label; validation and server errors are announced.
You cannot delete yourself. Audit-logged as user.delete with the
email snapshotted and the per-organisation decisions.
On 12 September 2026 a sole owner was deleted in a two-step confirm that never mentioned the organisation. It was left ownerless and every page for the one account still pointed at it failed with "Uh oh, something went wrong" until a fix was deployed. The single modal above replaced that flow.
What staff cannot edit
Personal details — name, email, username, phone — are read-only in the detail panel. Staff can suspend, promote, and manage org memberships, but a user edits their own identity from their own profile.
Gotchas
- "Pending" ≠ "inactive". Invited accounts are active with no password; the Status filter won't find them — look for the Resend invite action instead.
- Setup links are one-shot. Resending invalidates nothing you need, but the user must use the newest link.
- Bulk create never sets passwords and never adds to an org.
- Deactivate vs delete. Deactivate to block sign-in reversibly; delete only when the data must go (and note org ownership is reassigned, not deleted).
What's next
- Platform admin overview → — the rest of the Platform section
- Mark a user as verified → — the email-gate override
- Members → — adding created users to an organization
- Security model → — staff vs superuser scope