Users & Roles
Gatewyse organizes users into a deployment → organization → department hierarchy with role-based access control. This page covers user management, role assignments, the scope hierarchy, and single sign-on integration.
User Management
User List
The Users page displays a searchable, paginated table with filters for status and role:
| Column | Description |
|---|---|
| Name | The user’s display name |
| Login email address | |
| Status | active, inactive, or suspended |
| Roles | Assigned role tags |
| Last Login | Most recent login timestamp, or “Never” |
Use the Status and Role dropdown filters to narrow the list. The search bar matches against name and email.
Creating a User
Click Create User to open the form with these fields:
- Name — The user’s full name
- Email — Must be unique across the system
- Password — Initial password (users can change it after first login)
- Role — One or more roles, chosen from the roles that actually exist on this deployment: the built-in roles listed below plus any custom roles defined on the Roles page. At least one is required — an account with no role reaches nothing, and the form says so rather than letting you create one by accident.
Editing a User
Click Edit to navigate to the user detail page where you can update the user’s name, email, password, role assignments, and status.
User Statuses
| Status | Effect |
|---|---|
active | User can log in and make requests |
inactive | User account is disabled; login is blocked |
suspended | User account is temporarily suspended by an administrator |
How authority actually works
A user does not have a list of roles. A user has a list of memberships, and each membership is a scope plus the roles held in that scope.
memberships: [ { scopeKind: 'organization', scopeId: '…', roleIds: […], source: 'local' }, { scopeKind: 'department', scopeId: '…', roleIds: […], source: 'sso' }]scopeKind is system, organization or department. source is local,
sso or scim — which is how an administrator’s edit can replace the
hand-managed grant without erasing one an identity provider wrote, and vice
versa.
This is the model the rest of the page depends on, so three consequences are worth stating outright.
A session acts in one scope at a time. When you sign in, the gateway picks a default scope, collects the roles from every membership at that scope, and bakes the resulting permissions into the access token. Roles held elsewhere are not in play until you switch. See the scope switcher below.
The scope recorded on a role is descriptive. No enforcement path reads it.
Containment comes from the scope the session is acting in, checked against the
target of every request. A role written for the deployment, held by an account
pinned to a department, confers department-level reach — it reads wide and acts
narrow. See
Organizations & Departments.
A role change takes effect at the next token issue. Permissions are resolved when a token is minted, not per request, so a revoked role stops applying at the user’s next sign-in, refresh or scope switch.
The flat role list you see on a user and in the console is a display of the locally-managed membership. Editing a user’s roles or its organization or department recomputes that membership from the merged result — writing one without the other is how a revoked role used to stay in force.
Roles
Roles control what actions a user can perform in the admin dashboard and through the API. The gateway ships with system roles and supports custom role definitions.
Built-in Roles
| Role | Scope | Typical Permissions |
|---|---|---|
| super-admin | Global | Unrestricted access to every area of the deployment, including system internals and queues |
| deployment-admin | Deployment | Manage providers, models, routing, guards, budgets and users across the deployment — everything except system internals and queues |
| org-admin | Organization | Manage users and budgets within their organization |
| dept-admin | Department | Manage users and budgets within their department |
| user | Self | Make API requests within assigned budget limits |
| read-only | Deployment | View dashboards, usage, and configuration without making changes |
Custom Roles
The Roles page (/roles) lets you create custom roles with fine-grained permissions. Each role defines:
- Name and slug — Human-readable name and a URL-safe identifier
- Description — What the role is intended for
- Scope —
system,organizationordepartment. Descriptive: it documents the level the role was written for and is not itself enforced. - Permissions — A list of resource/action pairs (e.g.
providers: [read, write],budgets: [read]) - System flag — System roles cannot be deleted or modified
The scope switcher
The header of every admin page carries a readout of the scope the session is currently acting in — Deployment, or the name of an organization or department. It is always visible, even when there is only one option, because “which scope am I looking at” is a question every page’s contents depend on.
Where more than one scope is available it is also a menu. Choosing one re-issues the session and reloads the console: every page on screen holds data resolved for the scope being left, so a targeted refresh would leave some of it stale.
List the scopes available to the session
GET /api/admin/auth/me/scopes{ "data": { "active": { "kind": "organization", "id": "665f..." }, "authority": { "kind": "local" }, "available": [ { "kind": "system", "label": "Whole deployment" }, { "kind": "organization", "id": "665f...", "label": "Acme Corp" } ] }}Any authenticated session may call it; there is no permission attached, because it only ever reports on the caller.
Switch scope
POST /api/admin/auth/me/scopeContent-Type: application/json
{ "kind": "organization", "id": "665f..." }kind is system, organization or department; id is omitted for
system. On success both the access and refresh tokens are re-issued into the
session cookies, carrying the new scope and a permission set re-aggregated
from the roles held in that scope alone. Re-issue is the point: the
permissions in the old token were aggregated for the old scope, so keeping it
would carry the old reach into the new context.
Requesting a scope the session cannot reach is refused with 403 — “That scope
is not available to this session. Sign in again at the level you need.”
Authority bounds what is reachable
Which scopes appear in available depends on how the session authenticated,
reported as authority:
| Authority | Reaches |
|---|---|
local | Every scope the account holds a membership in |
system-idp | The same — the deployment’s own identity provider is trusted deployment-wide |
org-idp | Only that organization and its departments. Never system. |
So signing in through an organization’s identity provider cannot yield deployment authority, however wide the account’s memberships are. The console says as much in the menu when that applies. To reach the other scopes, sign in at deployment level instead.
Scope hierarchy
The gateway organizes users into a three-level hierarchy:
Deployment +-- Organization +-- Department +-- UserThe deployment
The root of the cascade, and implicit in every request — there is no object to manage and no page for it. Providers, models and the default policy are configured once for the deployment on the Settings page, and organizations and departments inherit them.
The organization is the first level that discriminates anything: two organizations can see different catalogues, route differently, and authenticate against different identity providers. A deployment cannot, because there is only one.
Organizations and Departments
Organizations group related departments, and departments group users. Each may narrow what it inherits — never widen it. These levels provide scoping for:
- Budget enforcement (spend limits at each level)
- Role-based access (org-admins and dept-admins)
- Provider and model availability, and per-organization credentials
- Usage reporting and audit trails
Managing them has its own page: see Organizations & Departments.
Authentication
Password Authentication
Users log in with email and password. The server issues HttpOnly cookies for session management with automatic token refresh. MFA (multi-factor authentication) is supported — when enabled, the login flow prompts for an MFA code after password verification.
Single Sign-On (SSO)
SSO is configured on the organization — open it and use the Sign-in and
provisioning section, or PUT /api/admin/organizations/:id/sso. The
deployment-wide configuration on the Settings page serves the deployment’s
own sign-in, under the reserved slug system.
An organization with no SSO configuration of its own does not fall back to the deployment’s. Borrowing it would mean being authenticated by an authority entitled to deployment scope, so the sign-in is refused instead. See Organizations & Departments.
The gateway supports two protocols:
OpenID Connect (OIDC)
Configure with:
- Issuer URL (e.g.
https://accounts.google.com) - Client ID and Client Secret
- Scopes (default:
openid email profile)
SAML 2.0
Configure with:
- IdP SSO URL (entry point)
- IdP Entity ID (issuer)
- IdP signing certificate (X.509 PEM format)
After SSO is enabled, the login page accepts an organization slug and sends the
user to /api/auth/sso/<slug>/initiate. The callback is handled at
/auth/sso-callback; tokens are set as HttpOnly cookies and never appear in the
URL. User accounts are automatically provisioned on first SSO login, with roles
taken from the organization’s groupRoleMappings where configured.
Multi-Factor Authentication (MFA)
Gatewyse supports TOTP-based multi-factor authentication (compatible with authenticator apps such as Google Authenticator, 1Password, or Authy). Enrollment is a two-step confirm-before-activate flow, so a pending secret never blocks login until the user proves possession of it. All MFA endpoints act on the authenticated user’s own account (/me).
Enrollment
1. Begin setup
POST /api/admin/auth/me/mfa/setupGenerates a pending TOTP secret and returns an otpauth:// URI (render it as a QR code for the authenticator app) plus the base32 secret for manual entry:
{ "data": { "secret": "JBSWY3DPEHPK3PXP", "otpauthUri": "otpauth://totp/AI%20Gateway:user@example.com?secret=..." }}The secret stays pending — mfaEnabled remains false and login is unaffected — until the code is confirmed. Setup is rejected if MFA is already enabled; disable it first to re-enroll.
2. Confirm and enable
POST /api/admin/auth/me/mfa/enableContent-Type: application/json
{ "code": "123456" }Verifies the code against the pending secret, activates MFA, and returns a set of one-time recovery codes shown exactly once (store them securely; they are the fallback if the authenticator is lost):
{ "data": { "recoveryCodes": ["abcde-fghij", "klmno-pqrst", "..."] } }Disabling MFA
POST /api/admin/auth/me/mfa/disableContent-Type: application/json
{ "code": "123456" }Disabling requires proof of possession — a current TOTP code or an unused recovery code — so a hijacked session cannot silently strip a user’s second factor.
Regenerating recovery codes
POST /api/admin/auth/me/mfa/recovery-codesContent-Type: application/json
{ "code": "123456" }Requires a current TOTP or recovery code, invalidates the previous set, and returns a fresh set of codes exactly once.
Once MFA is enabled, the login flow prompts for a code (mfaCode) after the password is verified.
Session Management
Each login establishes a session family backed by a rotating refresh token. Users can review and revoke their own sessions; administrators can force-logout any user within their own scope.
User self-service
List active sessions
GET /api/admin/auth/me/sessionsReturns the caller’s active sessions, each with its sid, createdAt, lastUsedAt, and (where captured) the originating ip and userAgent. Expired sessions are pruned as they are listed.
Revoke one session
DELETE /api/admin/auth/me/sessions/:sidRevokes a single session by its sid — useful for signing out a lost or unrecognized device.
Admin force-logout
An administrator can revoke all of a user’s sessions at once (scope-bounded — only for users the caller may administer; an out-of-scope target is refused and the refusal is audited):
POST /api/admin/users/:id/revoke-sessionsRequires the users:update permission and returns the count of sessions revoked.
Refresh-token rotation and reuse detection
Refresh tokens are rotated on every use and tracked per session family. If a rotated-out (already-superseded) refresh token is ever presented again, that is treated as token theft: the entire session family is revoked, not just the replayed token. The replay is logged and the request is rejected.