S
supero.docs
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.
RoleLevelScopeWhat it can do
platform_admin0globalSupero staff only. Full access across every domain.
platform_user0globalSupero staff only. Identical permissions to platform_admin, kept as a separate label for staff accounting.
domain_admin1domainOwns a domain: every project, tenant, user, and schema in it.
domain_user2domainDomain-wide read plus full CRUD. No admin/management rights.
project_admin3projectManages every tenant in a project; owns schema management.
project_user4projectProject-wide read plus full CRUD. No admin rights.
developer5projectTechnical 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_admin6tenantManages one tenant's users and settings. Can read schemas but not define them.
tenant_user7tenantRegular business user -- full CRUD inside their own tenant, no admin rights.
viewer8tenantRead-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 & PathPurpose
POST /api/v1/users/registerCreate 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}/roleChange a user's role.
PUT /api/v1/users/{user_uuid}/enableRe-enable a disabled user.
PUT /api/v1/users/{user_uuid}/disableDisable a user (soft delete -- they can no longer log in).
POST /api/v1/users/{user_uuid}/move-to-tenantMove a user to a different tenant.
GET /api/v1/domains/{domain_name}/usersList users in a domain, filterable by role, tenant, and project.
GET /api/v1/projects/{project_uuid}/usersList 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.
TokenDefault lifetime
Access token8 hours
Refresh token7 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