User Provisioning¶
This guide covers creating and managing user accounts programmatically - useful for onboarding teams, automating employee lifecycle events, or integrating with an identity provider.
Prerequisites¶
| Requirement | Detail |
|---|---|
| Auth | Active session + CSRF token |
| Privilege - read users/roles | Users / View |
| Privilege - create/edit users | Users / Edit |
Step 1 - List Available Roles¶
Every user must be assigned a role. Fetch the role list to get role IDs before creating users.
Response:
{
"data": {
"rows": [
{ "id": 1, "Role Name": "Superuser", "Role Description": "Full system access" },
{ "id": 3, "Role Name": "Procurement Manager", "Role Description": "Full procurement read/write, demand read" },
{ "id": 5, "Role Name": "Demand Planner", "Role Description": "Sales orders and forecasts read/write" },
{ "id": 7, "Role Name": "Viewer", "Role Description": "Read-only access to all modules" }
]
}
}
Note the id for each role - you'll pass this as Role Id when creating a user.
Step 2 - Create a User¶
CSRF=$(grep csrf_access_token cookies.txt | awk '{print $NF}')
curl -b cookies.txt -X POST \
"https://acme.knosc.com/api/user" \
-H "Content-Type: application/json" \
-H "X-XSRF-TOKEN: $CSRF" \
-d '{
"User Fullname": "Sarah Chen",
"Username": "sarah.chen@acme.com",
"User Email": "sarah.chen@acme.com",
"User Phone": "+1 555 200 3000",
"Role Id": 3,
"password": "Welcome2024!"
}'
Response:
The user will be prompted to change their password on first login.
Step 3 - Verify the User Was Created¶
{
"data": {
"id": 88,
"User Fullname": "Sarah Chen",
"Username": "sarah.chen@acme.com",
"User Email": "sarah.chen@acme.com",
"User Phone": "+1 555 200 3000",
"Role Id": 3,
"Role Name": "Procurement Manager",
"User Status": "Active"
}
}
Step 4 - Update a User's Role¶
If a user changes teams or responsibilities, update their role with PUT:
curl -b cookies.txt -X PUT \
"https://acme.knosc.com/api/user/88" \
-H "Content-Type: application/json" \
-H "X-XSRF-TOKEN: $CSRF" \
-d '{"Role Id": 5}'
Only the fields included in the request body are modified.
Step 5 - Deactivate a User (Offboarding)¶
Set User Status to Inactive rather than deleting the account - this preserves audit history and any records created by that user.
curl -b cookies.txt -X PUT \
"https://acme.knosc.com/api/user/88" \
-H "Content-Type: application/json" \
-H "X-XSRF-TOKEN: $CSRF" \
-d '{"User Status": "Inactive"}'
Inactive users cannot log in but their historical data remains intact.
Bulk Provisioning - Python Example¶
import requests
BASE_URL = "https://acme.knosc.com/api"
def provision_users(session, users: list[dict]) -> list[dict]:
"""
users: list of dicts with keys:
fullname, username, email, phone (optional), role_id, password
Returns list of results with user id or error per entry.
"""
csrf = session.cookies.get("csrf_access_token")
results = []
for user in users:
payload = {
"User Fullname": user["fullname"],
"Username": user["username"],
"User Email": user["email"],
"Role Id": user["role_id"],
"password": user["password"],
}
if user.get("phone"):
payload["User Phone"] = user["phone"]
response = session.post(
f"{BASE_URL}/user",
headers={"X-XSRF-TOKEN": csrf},
json=payload,
)
body = response.json()
if response.status_code == 200:
results.append({"username": user["username"], "id": body["id"], "status": "created"})
print(f" Created: {user['username']} (id={body['id']})")
else:
results.append({"username": user["username"], "error": body.get("Message"), "status": "failed"})
print(f" Failed: {user['username']} - {body.get('Message')}")
return results
# Fetch roles once
roles = {
r["Role Name"]: r["id"]
for r in session.get(f"{BASE_URL}/role").json()["data"]["rows"]
}
# Define users to provision
new_users = [
{"fullname": "Alice Nguyen", "username": "alice.nguyen@acme.com", "email": "alice.nguyen@acme.com", "role_id": roles["Procurement Manager"], "password": "Welcome2024!"},
{"fullname": "Bob Okafor", "username": "bob.okafor@acme.com", "email": "bob.okafor@acme.com", "role_id": roles["Demand Planner"], "password": "Welcome2024!"},
{"fullname": "Cate Patel", "username": "cate.patel@acme.com", "email": "cate.patel@acme.com", "role_id": roles["Viewer"], "password": "Welcome2024!"},
]
results = provision_users(session, new_users)
created = [r for r in results if r["status"] == "created"]
failed = [r for r in results if r["status"] == "failed"]
print(f"\n{len(created)} created, {len(failed)} failed")
Password Policy¶
| Rule | Requirement |
|---|---|
| Minimum length | 8 characters |
| Format | Any printable characters |
| First-login reset | Users are prompted to set a new password on their first successful login |
Set a temporary password when provisioning; users change it on first sign-in. Avoid sending the temporary password in plain text - use a secure channel (e.g. encrypted email, 1Password Send).
Checking Existing Users Before Provisioning¶
To avoid User.AlreadyExists errors, check if a username or email is already in use before calling POST:
users = session.get(f"{BASE_URL}/user").json()["data"]["rows"]
existing_emails = {u["User Email"] for u in users}
to_create = [u for u in new_users if u["email"] not in existing_emails]
already_exists = [u for u in new_users if u["email"] in existing_emails]
print(f"{len(to_create)} to create, {len(already_exists)} already exist")
Common Errors¶
| Code | Cause | Fix |
|---|---|---|
User.AlreadyExists |
Username or User Email already taken |
Check existing users first; use a unique identifier |
User.PasswordTooShort |
Password shorter than 8 characters | Use a longer temporary password |
Role.NotFound |
Role Id does not match any role |
Fetch roles first and use the correct id |
User.NotPrivileged |
Your account lacks Users / Edit privilege |
Contact your administrator |