Documentation/Managing Your App/Users & Roles
Users & Roles
Create and manage user accounts, assign built-in and custom roles, and understand how sessions and role-assignment limits work in Supero.
Overview
Every user account in Supero lives under a tenant (Domain -> Project -> Tenant -> UserAccount) and carries exactly one role from a fixed, closed catalog. The role determines two things: a coarse permission list (what resource types the user can touch at all) and, for your own app schemas, a finer per-entity access policy. This page covers account lifecycle -- creating, listing, updating, enabling/disabling, and moving users -- plus the role catalog and the rules around who can assign which role to whom.
For the full mechanics of the two permission layers (coarse role permissions vs. per-schema access policies), see the Access Control (RBAC) page. This page focuses on managing accounts and roles themselves.
Quick Start
Log in to get a session, then create a user under your tenant. The REST examples below work from any language; a Python example using the SDK follows.
http
POST https://api.supero.dev/api/v1/auth/login
Content-Type: application/json
{
"domain": "acme-corp",
"project": "storefront",
"tenant": "northwind",
"email": "[email protected]",
"password": "secret123"
}http
POST https://api.supero.dev/api/v1/users/register
Authorization: Bearer <jwt>
Content-Type: application/json
{
"domain_name": "acme-corp",
"email": "[email protected]",
"password": "initial-password-123",
"first_name": "Jane",
"last_name": "Smith",
"role": "tenant_user",
"tenant_uuid": "<tenant-uuid>"
}The Python SDK wraps the same endpoints under org.users. login() authenticates and returns a connected client whose domain/project/tenant context is reused automatically by every org.users call:
python
from supero import login
org = login(
domain_name="acme-corp",
email="[email protected]",
password="secret123",
project="storefront",
)
user = org.users.add_user(
email="[email protected]",
password="initial-password-123",
first_name="Jane",
last_name="Smith",
role="tenant_user",
)
print(user["user_uuid"])ℹ️ pip install supero
Install the Python SDK with pip install supero. You can also connect with an existing API key instead of a password: supero.connect(domain_name=..., api_key='ak_...').
The Role Catalog
Roles are a fixed, closed set -- there is no free-form role string, and you cannot invent a new privilege level. Lower hierarchy level means more privileged. platform_admin and platform_user are Supero-staff-only roles used in the SaaS control plane; they are not customer-assignable and you should not expose them as an option in your app's own user management UI.
| Role | Level | Scope | What it can do |
|---|---|---|---|
| platform_admin | 0 | global | Supero staff only. Full access across every domain. |
| platform_user | 0 | global | Supero staff only. Identical permissions to platform_admin, kept as a separate label for staff accounting. |
| domain_admin | 1 | domain | Owns a domain: every project, tenant, user, and schema in it. |
| domain_user | 2 | domain | Domain-wide read plus full CRUD. No admin/management rights. |
| project_admin | 3 | project | Manages every tenant in a project; owns schema management. |
| project_user | 4 | project | Project-wide read plus full CRUD. No admin rights. |
| developer | 5 | project | Technical role: manages schemas and SDKs, with CRUD inside its own tenant for building and testing. It does not read across tenants. Not a business admin -- cannot manage users. |
| tenant_admin | 6 | tenant | Manages one tenant's users and settings. Can read schemas but not define them. |
| tenant_user | 7 | tenant | Regular business user -- full CRUD inside their own tenant, no admin rights. |
| viewer | 8 | tenant | Read-only access within a tenant. |
A user's assigned role is stamped on login as both role and base_role (see Custom Roles below for why there are two fields). The response also carries a derived permissions list, a ui_view (admin or customer), and a computed scope -- your app should gate its UI off these server-shipped values rather than re-deriving them.
Custom Roles
You can register a custom role name -- for example "patient" or "dentist" -- so your app and its schemas can use domain-specific labels. A custom role adds no new permissions: it is a named alias that resolves to one of three base roles for every real access decision (permissions, ui_view, and policy defaults).
- •A custom role's base_role must be tenant_admin, tenant_user, or viewer -- that is the privilege ceiling. A custom role can never be more powerful than a built-in tenant_admin.
- •You cannot shadow a built-in role name, and a creator cannot base a new custom role on something more privileged than their own role.
- •At login, the server resolves your custom role to its base_role and uses the base_role for permissions, UI surface, and access-policy defaults. Your account still shows the custom name in role; base_role carries the resolved built-in.
Custom roles are registered and managed as role objects through the CRUD API; see Access Control (RBAC) for the full structure and examples.
Creating and Managing Users
User accounts are managed through a dedicated set of REST endpoints (not the generic CRUD API), because registration, role changes, and enable/disable all carry extra validation -- password hashing, role-hierarchy checks, and tenant-isolation rules.
| Method & Path | Purpose |
|---|---|
| POST /api/v1/users/register | Create a new user under a tenant. |
| GET /api/v1/users/{user_uuid} | Get a user's details. |
| PUT /api/v1/users/{user_uuid} | Update a user's own editable profile fields. |
| PUT /api/v1/users/{user_uuid}/role | Change a user's role. |
| PUT /api/v1/users/{user_uuid}/enable | Re-enable a disabled user. |
| PUT /api/v1/users/{user_uuid}/disable | Disable a user (soft delete -- they can no longer log in). |
| POST /api/v1/users/{user_uuid}/move-to-tenant | Move a user to a different tenant. |
| GET /api/v1/domains/{domain_name}/users | List users in a domain, filterable by role, tenant, and project. |
| GET /api/v1/projects/{project_uuid}/users | List users across every tenant in a project. |
Registering a user
email, password, and domain_name are required. role defaults to viewer if omitted. Scope the new account with tenant_uuid (or tenant_name/tenant) -- if you omit both project and tenant, the caller's own tenant/project is used.
http
POST https://api.supero.dev/api/v1/users/register
Authorization: Bearer <jwt>
Content-Type: application/json
{
"domain_name": "acme-corp",
"email": "[email protected]",
"password": "SecurePass123!",
"first_name": "Bob",
"last_name": "Lee",
"role": "viewer",
"tenant_uuid": "<tenant-uuid>"
}A successful create returns 201 with the new user's uuid, role, and tenant context. Registering with an email that already exists in that tenant returns 409.
Changing a role, enabling/disabling, listing
python
# Promote a user (subject to the assignment ceiling below)
org.users.assign_role("<user-uuid>", "tenant_admin")
# Disable / re-enable an account
org.users.disable_user("<user-uuid>")
org.users.enable_user("<user-uuid>")
# Look up one user
user = org.users.get_user("<user-uuid>")http
GET https://api.supero.dev/api/v1/domains/acme-corp/users?role=tenant_user&tenant_uuid=<tenant-uuid>
Authorization: Bearer <jwt>⚠️ Disable, not delete
There is no permanent-delete endpoint for user accounts. disable_user (PUT .../disable) is how you remove someone's access -- the account and its history stay in place and can be re-enabled later.
ℹ️ Your own profile
GET /api/v1/auth/me returns the currently authenticated user's details from their access token. PUT /api/v1/users/{user_uuid} lets a user update their own editable profile fields (not role or permissions).
Role Assignment Rules
For the complete set, see Roles & Permissions Reference.
Both user registration and role changes enforce the same ceiling: you can only assign a role that is strictly less privileged than your own (a higher hierarchy-level number). A tenant_admin (level 6) can create tenant_user or viewer accounts, but cannot create another tenant_admin or anything above it. platform_admin/platform_user (level 0) are the one exception -- they may mint a peer level-0 admin, since otherwise there would be no way to create a second platform-level administrator.
- •Attempting to assign a role at or above your own level returns 403.
- •The last enabled platform-level admin (platform_admin or platform_user) in a domain cannot be demoted or disabled -- including by themselves -- so a domain is never left with no administrator.
- •A custom role is resolved to its base_role on both sides of the check, so a custom-role admin's ceiling is based on what their custom role actually grants, not the literal role string.
Separately, whether a role routes a user to your app's admin surface or its customer surface is decided by ui_view in the login response: platform_admin, platform_user, domain_admin, project_admin, tenant_admin, and developer resolve to the admin UI; tenant_user and viewer resolve to the customer UI. This is computed from the resolved base_role, so a custom role built on tenant_admin also lands on the admin surface.
Sessions and Authentication
Logging in returns a short-lived access token and a longer-lived refresh token. Send the access token as Authorization: Bearer <jwt> on subsequent requests.
| Token | Default lifetime |
|---|---|
| Access token | 8 hours |
| Refresh token | 7 days |
json
{
"auth": {
"access_token": "...",
"refresh_token": "...",
"token_type": "Bearer",
"expires_in": 28800,
"session_id": "..."
},
"user": {
"role": "tenant_admin",
"base_role": "tenant_admin",
"permissions": ["crud:create", "crud:read", "user:read", "..."],
"ui_view": "admin"
},
"context": {
"scope": { "level": "tenant", "tenant_uuid": "..." }
},
"policy": { "default_access": "full", "is_admin": false, "entities": { "...": "..." } }
}Refresh the access token before it expires with your existing refresh token, and revoke a session on logout:
http
POST https://api.supero.dev/api/v1/auth/refresh
Authorization: Bearer <refresh-token>http
POST https://api.supero.dev/api/v1/auth/logout
Authorization: Bearer <jwt>A logged-in user can change their own password with the current one, without needing a reset flow:
http
POST https://api.supero.dev/api/v1/auth/change-password
Authorization: Bearer <jwt>
Content-Type: application/json
{
"old_password": "current-password",
"new_password": "new-secure-password"
}For a forgotten password, POST /api/v1/auth/forgot-password with an email sends a one-time code; POST /api/v1/auth/verify-reset-otp verifies it and returns a reset token; POST /api/v1/auth/reset-password with that token and a new_password completes the reset.
ℹ️ API keys are a separate credential
For server-to-server or scripted access, use an API key sent as the X-API-Key header instead of a JWT. Never send an API key as a Bearer token -- the two credential types are validated by different code paths.
Next Steps
- •Access Control (RBAC) -- the two permission layers roles feed into, plus object-level access policies and owner-scoping
- •Projects & Tenants -- how the domain/project/tenant hierarchy that scopes every user account is organized
- •Schema Reference -- the field and reference model used across the CRUD API
- •Security -- client-side field encryption, audit logging status, and other workspace-level security controls
On this page