LiteBot

Users, roles and tenants

This chapter explains how people and programs sign in to LiteBot, what each role may do, and how tenants keep one customer's bots and data apart from another's. Read it before you create the first real user, before you hand out an API key, and before you host bots for more than one customer on one server.

How access works

Who Signs in with Sent as
A person using the dashboard Username and password, exchanged for a JWT Authorization: Bearer <token>
A program (CRM, gateway, script) An API key that starts with lbk_ X-API-Key: lbk_...
A browser opening /voice The same JWT or API key ?token=<token> or ?api_key=lbk_... in the URL

Every user and every API key has exactly one role and belongs to exactly one tenant. The role says what it may do; the tenant says which bots and data it may do it to.

Some endpoints need no sign-in at all: POST /api/chat, the /chat WebSocket and /health. See Security.

Logging in

POST /api/auth/login takes a username (3 to 50 characters) and a password (at least 6). Usernames are matched in lower case.

curl -s -X POST http://127.0.0.1:3345/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"ops","password":"<password>"}'
{
  "success": true,
  "data": {
    "user": { "id": "6717...", "username": "ops", "role": "tenant-admin", "tenantId": "acme",
              "permissions": ["bots:create", "bots:read", "..."] },
    "token": "<token>",
    "refreshToken": "<refresh-token>",
    "expiresIn": "24h"
  }
}

A wrong password, an unknown user and a deactivated user all get 401 Invalid credentials.

Token Lifetime Use
Access token (token) JWT_EXPIRES_IN, default 24h Every authenticated request
Refresh token 7 days, fixed POST /api/auth/refresh with {"refreshToken":"..."} returns a new pair

On refresh, LiteBot re-reads the user's role, tenant and status from the database, so a role change, a tenant move or a deactivation takes effect at the next refresh. An access token already issued stays valid until it expires: to lock a user out at once, deactivate them and keep JWT_EXPIRES_IN short.

GET /api/auth/me returns the caller's id, username, role, tenant, permissions and authMethod (jwt or api-key).

Both tokens are signed with JWT_SECRET. Changing it logs everyone out; see JWT_SECRET.

The bootstrap admin

On a new install there are no users in MongoDB. You sign in the first time with an admin defined in .env:

Variable Default Meaning
ADMIN_USERNAME admin Username of the bootstrap admin
ADMIN_PASSWORD_HASH unset scrypt hash of its password, as hex salt:hash. Unset disables the bootstrap login

The hash uses scrypt with N=16384, r=8, p=1, a 32-byte salt and a 64-byte key. Generate one with Python 3:

python3 -c "import hashlib,os,getpass;s=os.urandom(32);print(s.hex()+':'+hashlib.scrypt(getpass.getpass().encode(),salt=s,n=16384,r=8,p=1,dklen=64).hex())"

Paste the output into .env and restart. The bootstrap admin has role admin and no tenant.

The bootstrap login is used only while no database user has the same username. Once you create a database user called admin, the .env entry stops working for login. Create a named database admin for each operator, then clear ADMIN_PASSWORD_HASH.

Built-in roles

LiteBot seeds three system roles at every start-up and resets their permissions to the shipped list. You cannot edit or delete them.

Role Scope Permissions
admin Global: every tenant Everything. The server skips permission checks for this role
tenant-admin Own tenant create, read, update, delete on bots, conversations, api-keys, documents, collections, users, tools, campaigns; plus roles:read
user Own tenant bots:read, documents:read, collections:read

A permission is <resource>:<action>. The resources are bots, documents, collections, admin, api-keys, roles, conversations, campaigns, users and tools; the actions are create, read, update and delete. GET /api/auth/roles/list/permissions returns the full list.

Some things only the admin role can do, whatever permissions another role holds:

  • create tenants and change a tenant's shared tools;
  • create, change and delete roles;
  • everything under /api/admin (providers, hot swap, config files) and /health/detail;
  • reload tools and bots from disk, write disk tools, run the provider health check;
  • change the dialer's default endpoint and the voice catalog;
  • give a user or an API key the admin role;
  • move a bot to another tenant.

Custom roles

A global admin can create roles that grant any mix of the permissions above. Custom roles are global: one role list serves all tenants.

curl -s -X POST http://127.0.0.1:3345/api/auth/roles \
  -H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
  -d '{"name":"analyst","description":"Read bots and conversations","permissions":["bots:read","conversations:read"]}'
Method and path Needs Notes
GET /api/auth/roles roles:read All roles with their permissions
GET /api/auth/roles/:id roles:read :id is the role's database id
POST /api/auth/roles admin Name: 2 to 50 letters, digits, -, _. An unknown permission gets 400; a taken name gets 409
PUT /api/auth/roles/:id admin Change description or permissions. A system role gets 403
DELETE /api/auth/roles/:id admin 409 while any user or unrevoked API key has the role

Role permissions are cached for ROLE_CACHE_TTL seconds (default 30). A change can take that long to reach users who are signed in.

Users

Method and path Needs Notes
GET /api/users users:read A tenant caller sees only its own tenant, and never admin users
GET /api/users/:id users:read
POST /api/users users:create See below
PUT /api/users/:id users:update role, email, displayName, active, tenantId
PUT /api/users/:id/password users:update {"password":"..."}, 6 to 100 characters
DELETE /api/users/:id users:delete
curl -s -X POST http://127.0.0.1:3345/api/users \
  -H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
  -d '{"username":"reception","password":"<password>","role":"user","email":"desk@example.com","tenantId":"acme"}'
{ "success": true, "message": "User created successfully",
  "data": { "id": "6717...", "username": "reception", "role": "user", "active": true, "tenantId": "acme" } }

Rules the server enforces:

  • Usernames are 3 to 50 letters, digits, - and _, stored in lower case. A taken username gets 409.
  • The role and the tenant must exist (400 otherwise).
  • Only a global admin may set tenantId. A tenant admin's new users always land in the tenant admin's own tenant, and a tenantId they send on update is ignored. A global admin who leaves it out gets tenant default.
  • Only a global admin may give the admin role (403 otherwise).
  • You cannot change your own role, deactivate yourself, or delete yourself.
  • A user outside the caller's tenant reads as 404.
  • Passwords are stored as scrypt hashes and are never returned.

Set active to false to block a login without deleting the user's history.

API keys

API keys let programs call LiteBot without a password. Each key has a name, a role and a tenant, and acts with that role's permissions inside that tenant.

curl -s -X POST http://127.0.0.1:3345/api/auth/api-keys \
  -H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
  -d '{"name":"crm-integration","role":"tenant-admin","tenantId":"acme"}'
{
  "success": true,
  "message": "API key created successfully. Copy it now - it won't be shown again!",
  "data": { "id": "6718...", "name": "crm-integration", "role": "tenant-admin",
            "key": "lbk_3f9c...", "tenantId": "acme" }
}
  • The key is lbk_ followed by 32 hex characters. It appears only in this response. LiteBot stores its first 8 characters and a scrypt hash, so a lost key cannot be recovered: revoke it and create a new one.
  • Send it as X-API-Key: lbk_... on HTTP requests, or ?api_key=lbk_... when opening /voice.
  • tenantId is honoured only for a global admin; a tenant caller's keys always belong to its own tenant. Only a global admin may create an admin-role key, because such a key is unrestricted.
Method and path Needs Notes
GET /api/auth/api-keys api-keys:read Unrevoked keys in scope: name, role, tenant, created, last used, created by. Never the key
POST /api/auth/api-keys api-keys:create name (3 to 100 characters), role, optional tenantId
DELETE /api/auth/api-keys/:id api-keys:delete Marks the key revoked

Known limits:

  • A verified key is cached for API_KEY_CACHE_TTL seconds (default 60). A revoked key keeps working until its cache entry expires.
  • The create request accepts expiresAt and stores it, but the server does not check it when the key is used. Revoke keys you no longer need.
  • After API_KEY_FAIL_LIMIT bad keys (default 20) from one address within API_KEY_FAIL_WINDOW_MS (default 60000), further key attempts from that address are refused until the window passes.
  • Each key may hold API_KEY_DEFAULT_MAX_VOICE_CONCURRENT voice sessions at once (default 50).

Tenants

A tenant is a workspace: one customer's bots and everything that hangs off them. Tenant ids are 2 to 40 characters of lower-case letters, digits and -, starting with a letter or digit. LiteBot creates the tenant default at start-up; bots, users and keys without a tenant belong to it.

What is scoped to a tenant:

Data How it is scoped
Bots Each bot has a tenantId (default default)
Conversations, traces, analytics Through the bot that handled them, or a tenantId stamped on the row
Tools Database tools belong to one tenant or to all (*). Global tools reach a tenant only through its shared-tools list
Knowledge base collections A tenant sees the global collections and its own, and writes only to its own
Users and API keys Each has a tenantId
Dialer campaigns Each campaign belongs to a tenant
LLM provider keys Optional per-tenant keys; see below

A tenant caller never sees another tenant's rows: a bot, user, key or campaign outside its scope reads as 404.

Global admin and tenant admin

Global admin (admin) Tenant admin (tenant-admin)
Sees Every tenant, or one tenant with X-Tenant-Id Own tenant only
Creates tenants Yes No
Moves a bot to another tenant Yes, by setting tenantId on the bot No: its bots always stay in its tenant
Sets the tenant's LLM keys Any tenant Own tenant (needs api-keys:update)
Chooses shared global tools Yes No, read only

A global admin can narrow one request to a tenant by sending X-Tenant-Id: acme. The dashboard's tenant switcher does this (Viewing as a tenant). The header is ignored for everyone else; a malformed value gets 400.

Tenant endpoints

Method and path Needs Notes
GET /api/tenants signed in Global admin: all tenants. Others: their own
POST /api/tenants admin {"id":"acme","name":"Acme Clinics"}. A taken id gets 409
GET /api/tenants/:id/providers api-keys:read Supported providers and the configured ones, masked
PUT /api/tenants/:id/providers/:provider api-keys:update {"apiKey":"...","model":"..."}. Returns only the last 4 characters
DELETE /api/tenants/:id/providers/:provider api-keys:update Remove the tenant's key
GET /api/tenants/:id/usage?days=7 api-keys:read Spend and usage over 1 to 90 days (default 7)
GET /api/tenants/:id/shared-tools tools:read The tenant's shared-tools list
PUT /api/tenants/:id/shared-tools admin {"tools":["bookAppointment"]} or {"mode":"all"}

There is no endpoint to rename or delete a tenant.

curl -s -X POST http://127.0.0.1:3345/api/tenants \
  -H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
  -d '{"id":"acme","name":"Acme Clinics"}'

Tenant LLM keys

Per-tenant keys are supported for groq, openrouter, gemini, openai and sarvam. A tenant's bots use its own key for a provider when one is set, and the platform key from .env otherwise. Tenant keys are stored encrypted (see Encryption at rest) and are never returned in full.

Shared tools

A new tenant starts with an empty shared-tools list: none of the global tools are available to its bots until a global admin grants them. {"mode":"all"} gives the tenant every global tool, now and in future. The default tenant, created without a list, sees every global tool. The tenant's own database tools are always available to it.

Putting a bot in a tenant

  • A tenant user's new bots go into their own tenant.
  • A global admin picks the tenant in the bot form (or sends tenantId in POST /api/bots or PUT /api/bots/:botId). Leaving it out uses the admin's current tenant, which is default unless X-Tenant-Id is set.
  • Bot files under config/bots/ without a tenantId belong to default.

Where to go next