Managing users
Everything you can do to a project's users, from the dashboard (Project → Users) or from your backend with the secret key. Changes are recorded in the project's audit log.
Profile attributes
| Attribute | Rules | Who sets it |
|---|---|---|
name | 1–100 characters. Required at sign-up. | User, backend |
firstName | 1–100 characters. | User, backend |
lastName | 1–100 characters. | User, backend |
username | 3–32 of a–z 0–9 . _ -, starting and ending with a letter or digit. Stored lower-case, unique per project (USERNAME_TAKEN). | User, backend |
phoneNumber | International format; spaces, dashes and brackets are removed (+255712345678). | User, backend |
phoneNumberVerified | Tausi doesn't send SMS: your backend marks it after its own check. Changing the number resets it. | Backend |
locale | Language tag such as en, sw or sw-TZ. | User, backend |
timezone | IANA zone such as Africa/Dar_es_Salaam. | User, backend |
emailVerified | Set by the verification link, or by you. | Backend, dashboard |
role | user or admin. | Dashboard only |
Users can set the attributes at sign-up and edit them later (send null to clear one). Email changes aren't supported yet.
Metadata
Two JSON objects per user, each up to 8 KB, written only by your backend. Each write replaces the whole object.
| Field | Readable by | Use it for |
|---|---|---|
publicMetadata | The user (returned on sign-in, /users/me and session checks) and your backend | Plan, feature flags, onboarding step |
privateMetadata | Your backend and the dashboard only | Billing IDs, internal notes, risk scores |
await tausi.users.update(userId, {
publicMetadata: { plan: "pro", seats: 5 },
privateMetadata: { stripeCustomerId: "cus_123" },
});Sign-in state and activity
| Field | Meaning |
|---|---|
status | signed_in while the user has at least one unexpired session, otherwise signed_out. |
activeSessions | Number of unexpired sessions (devices). |
lastSignInAt | Latest successful sign-in: password, two-factor or social. |
lastActiveAt | Latest activity over all sessions (updated at most every few minutes). |
lastSignOutAt | Latest explicit sign-out. |
signInCount | Successful sign-ins so far. |
Ban and suspend
| Ban | Suspend | |
|---|---|---|
| Lasts | Until you unban | 1 minute to 1 year, then lifts by itself |
| On apply | Every session is revoked at once | Every session is revoked at once |
| Sign-in | Refused with ACCOUNT_BANNED | Refused with ACCOUNT_SUSPENDED (details.suspendedUntil) |
| Covers | Password, two-factor and social sign-in | Password, two-factor and social sign-in |
| Data | Kept | Kept |
- The refusal only comes after a correct password. A wrong password still gets
INVALID_CREDENTIALS, so a ban can't be discovered by guessing. - Social sign-in returns to your app with
?tausi_error=account_bannedoraccount_suspended. - The optional reason (up to 500 characters) is an internal note. Users are never shown it.
- Session checks from your backend (
verifySession,authenticateRequest) return null for blocked users. - Filter lists with
access=active,suspendedorbanned.
POST /api/v1/backend/users/:id/ban { "reason": "Spam" }
DELETE /api/v1/backend/users/:id/ban
POST /api/v1/backend/users/:id/suspend { "durationHours": 24, "reason": "Chargeback" } # or { "until": "2026-12-01T00:00:00Z" }
DELETE /api/v1/backend/users/:id/suspendBulk actions
In the dashboard, tick users (or the header box for the whole page) and pick an action. From your backend, send up to 100 IDs. Each user succeeds or fails on its own, and the response says which.
{ "action": "suspend", "ids": ["u1", "u2", "u3"], "durationHours": 72, "reason": "Fraud review" }
→ { "results": [{ "id": "u1", "ok": true }, { "id": "u3", "ok": false, "error": { "code": "NOT_FOUND", "message": "User not found." } }],
"succeeded": 2, "failed": 1 }Actions: delete, ban, unban, suspend, unsuspend, revoke_sessions, verify_email.
Sessions and devices
Open a user in the dashboard to see every active session with its device (browser and version, operating system, desktop / mobile / tablet, or a server-side SDK), IP address, sign-in time, last activity and expiry, and to revoke any of them. Below it, the sign-in history lists the last 50 sign-ins and sign-outs, including failed attempts and why they failed.
From your backend: GET /backend/users/:id returns sessions and signInHistory; POST /backend/users/:id/revoke-sessions signs the user out everywhere. Users can manage their own devices with listSessions, revokeSession and revokeOtherSessions in every client SDK.
Deleting users
Deleting removes the account, its credentials and every session permanently. The audit log keeps only the opaque user ID. In the dashboard you confirm by typing the user's email (or delete N for several) and entering your password.
In each SDK
| Action | Node | Python | PHP |
|---|---|---|---|
| List | users.list({ access }) | users.list(access=) | listUsers([...]) |
| Details | users.get(id) | users.get(id) | getUser($id) |
| Update | users.update(id, patch) | users.update(id, ...) | updateUser($id, [...]) |
| Ban | users.ban(id, { reason }) | users.ban(id, reason=) | banUser($id, $reason) |
| Unban | users.unban(id) | users.unban(id) | unbanUser($id) |
| Suspend | users.suspend(id, { durationHours }) | users.suspend(id, hours=) | suspendUser($id, $until) |
| Lift suspension | users.unsuspend(id) | users.unsuspend(id) | unsuspendUser($id) |
| Bulk | users.bulk(action, ids) | users.bulk(action, ids) | bulkUsers($action, $ids) |
| Sign out everywhere | users.revokeSessions(id) | users.revoke_sessions(id) | revokeUserSessions($id) |
| Delete | users.delete(id) | users.delete(id) | deleteUser($id) |
A secret key can't change roles; that's only possible in the dashboard.