Skip to main content

UserLoad

Manage the human (and service-account) users in a customer's Nuntiq tenant, for example to sync users from an upstream system (Workday, AD/Entra, an HRIS) into Nuntiq on a schedule.

:::caution Not available to connectors yet No connector entrypoint key may read or write users today. A UserLoad call from any entrypoint (also get_custom_N) gets a 403 missing_permission, in production and in the toolkit. Until a users entrypoint exists, sync users through the customer API with an API key. This page describes the class as it will work then, and the API it calls. :::

Import​

from lib.objects.user import UserLoad

Standard BaseLoad methods​

get_all(), search(), new(), save_all(), patch_all(), delete_keys(), index_by_source(), obj.save(), obj.delete(), obj.deactivate(). See BaseLoad.

Users are tenant-wide: an API key limited to organizations cannot read or write them.

Keys​

A user is found by its Nuntiq id, by source_system + source_record_id (the user's id in the HR or identity system), or by login_account.

User fields​

FieldTypeRequiredNotes
idstr(assigned)Server-assigned number, as text
first_namestryes
last_namestryes
emailstryesUnique across live users. A deleted user that holds the email is restored under the new login_account
login_accountstryesStable login handle. Unique. Natural key
login_typeintyes1 = username/password, 2 = SSO
passwordstrWrite-only, login_type 1 only. Stored as a hash; the user must change it at the next sign-in. Omit to keep the current password. A user switched to SSO loses its password
sso_providerstrWhen login_type = 2: the alias of the tenant's identity-provider configuration
is_activeboolDefault true. Kept when a POST leaves it out, so a feed never reactivates a user someone switched off
active_fromstr (ISO datetime)Kept when left out
active_tostr (ISO datetime)Kept when left out
send_inviteboolWrite-only. True emails an active password user a set-password link after the save
source_system / source_record_id / source_updated_atstrExternal identity, as on every engine resource
must_change_passwordbool(read-only)Set when a password is supplied
created_at / updated_atstr(read-only)

Deactivating or deleting a user ends its sessions and open set-password links.

UserGroupRef (nested)​

User.groups is a list of group memberships. Each entry references a group by its stable external_code.

FieldRequiredNotes
external_codeyesMust match an existing group on the tenant. An unknown code rejects the user (unknown_group)
name(read-only)Returned on GET for display; ignored on POST

Replace-all semantics: when you POST a user, the groups list (if present) replaces the user's memberships entirely. Leave it out to keep the memberships: a user without groups and without an assignment leaves the list out. To clear the memberships, assign an empty list (it is sent, also when empty) or patch the user with 'groups': []:

u.groups = [] # on a user you save
load.patch_all([{'login_account': 'jane.doe', 'groups': []}])

Building a user​

from lib.objects.user import UserLoad

def run(context):
load = UserLoad(context)

u = load.new()
u.first_name = 'Jane'
u.last_name = 'Doe'
u.email = 'jane.doe@example.com'
u.login_account = 'jane.doe'
u.login_type = 1
u.password = 'initial-temp-pw' # hashed by API; sets must_change_password

g1 = u.new_group()
g1.external_code = 'AP_TEAM'

g2 = u.new_group()
g2.external_code = 'FINANCE'

results = load.save_all()
return {'saved': len(results)}

Sync pattern: syncing from an upstream HRIS​

Upsert keyed on the HR system's id, run on a schedule:

def run(context):
load = UserLoad(context)

for upstream in fetch_users_from_hris(): # your code
u = load.new()
u.first_name = upstream['first_name']
u.last_name = upstream['last_name']
u.email = upstream['email']
u.login_account = upstream['login_account']
u.source_record_id = upstream['employee_id']
u.login_type = 2 # SSO
u.sso_provider = 'corp-okta'
# Replace group memberships from upstream
for code in upstream['team_codes']:
g = u.new_group()
g.external_code = code

load.save_all()

Existing users are updated; new ones are created. With save_all(full_load=True) the run is a sync run: users of your source system that the HR feed no longer has are deleted afterwards (nothing is removed when a user was rejected).

Exact-match filters: login_account, email, is_active, login_type, source_system, source_record_id, plus updated_since and include_deleted.

matches = load.search(login_account='jane.doe')
u = matches[0] if matches else None

for u in load.search(email='jane.doe@example.com'):
print(u.login_account, u.email)

# Active users only
for u in load.get_all(is_active=True): # sent as true
print(u.email)

Deactivate and delete​

# Switch a user off and keep it
load.patch_all([{'login_account': 'svc-leaving', 'is_active': False}])

# The same for a user you read: PATCHes is_active to false, does not delete
u.deactivate()

# Soft delete: also removes the group and processing-team memberships
load.delete_keys([{'login_account': 'svc-leaving'}])

deactivate_inactive(days=...)​

Bulk-deactivate users who haven't actually signed in for N days. Useful for license-cleanup jobs against tenants with many provisioned-but-unused accounts.

result = load.deactivate_inactive(
days=90,
exclude_login_accounts=['svc-erp', 'svc-edi'], # service accounts: skip
dry_run=False, # set True to preview
)

print(f"Deactivated {result['count']} user(s)")
for r in result['deactivated']:
print(f" {r['login_account']} (last login: {r['last_login_at']})")
ArgRequiredNotes
daysyesPositive integer. Both the staleness threshold AND the minimum account age: brand-new accounts that haven't had time to log in are skipped.
exclude_login_accountsnoList of login_account strings to skip
dry_runnoWhen True, returns the candidate list without making any changes

Returns a dict: {deactivated: [...], count, truncated, dry_run, days}. last_login_at reflects the most recent non-impersonation successful login; impersonation sessions don't count as activity.

truncated is True when more than 1000 users were affected (the deactivated list is capped at 1000 but count is accurate).

What's next​

  • BaseLoad: read/write patterns shared by every entity.
  • ApiClient → Error handling: ValidationError (a missing required field) and RecordsRejected (for example an unknown group or an email in use).