Let’s explore an example of creating (post_user_list) and updating (put_user) user profiles via API:
A user profile is constructed from basic fields (firstName, lastName, email, roles, …) and your custom defined profile fields (user fields). In order to construct the required payload the definition of your custom profile fields (user fields) is required. The recommended process is to fetch all the user fields in advance to ensure the data is valid. The below example shows how you can fetch user fields:
>>> user_fields = requests.get('https://api.kaizenep.com/v2/user-fields', headers=headers).json()
[
{
"_id": "id1",
"name": "Person College ID",
"fieldType": "string",
"isRequired": true
},
{
"_id": "id2",
"name": "Gender",
"fieldType": "discrete",
"isRequired": true,
"categories": [
{
"_id": "opt1",
"name": "Male"
},
{
"_id": "opt2",
"name": "Female"
}
]
}
]
In order to assign roles to a user a list of IDs has to be added to the payload. Role IDs can be obtained by using the roles endpoint (get_role_list). The below example shows how you can fetch the role IDs:
>>> roles = requests.get('https://api.kaizenep.com/v2/roles', headers=headers).json()
[
{
"_id": "roleid1",
"name": "Trainee",
...
},
{
"_id": "roleid2",
"name": "Assessor",
...
}
]
To enable the user to log in to risr/advance they must have credentials added to their account. There are two types of credentials:
Proxy type is used when authentication is done outside risr/advance such as when integrating with your SSO provider, and only the username is required to map the SSO user to the newly created risr/advance account. Username is in this case an agreed unique attribute provided by the authentication party (for example eduPersonPrincipalName - urn:oid:1.3.6.1.4.1.5923.1.1.1.6)
Local type is used when authentication is done within risr/advance and standard username and password is required.
By default a welcome email is sent to the newly created user. This behaviour can be suppressed by adding a corresponding flag to the options.
Having the list of user fields and roles we can construct the payload for creating a user:
You can upload a profile picture by sending it as a base64 encoded string along with the required content type.
Additionally, you can upload one or multiple files for a user field, with the file type depending on the specific settings of that user field.
An example of a payload is below:
>>> payload = {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@org.com",
"roles": ["roleid1"],
"userFields": [
{"_id": "id1", "value": "Users value for field 1"},
{"_id": "id2", "value": "opt2"},
{
'_id': "id_file_user_field",
'file': [
dict(
content=base64.b64encode(file.read()).decode("utf-8"),
contentType="png",
filename="name.png"
)
]
}
],
'profilePicture': dict(
content=base64.b64encode(image_file.read()).decode("utf-8"),
contentType='png'
)
"credentials": [
{"type": "proxy", "username": "sso_attribute_value"}
],
"options": {
"sendWelcomeEmail": false
}
}
>>> response = requests.post('https://api.kaizenep.com/v2/users', json=payload, headers=headers)
>>> response.json()
{"id": "new_profile_id", "rev": "revision number", "username": "new_kaizen_id"}
Important
User ID vs Username
Each user is given unique ID we refer to as a “kaizen id” or a “username”. This account information is used as a user identifier in all parts of risr/advance. All user profile data are stored in a separate object with it’s own ID we refer to as a “profile id” or a “user id”
There are multiple ways how to retrieve a user profile.
- Fetch by profile ID
>>> response = requests.get('https://api.kaizenep.com/v2/users/new_profile_id', headers=headers)
- Fetch by risr/advance ID
>>> response = requests.get('https://api.kaizenep.com/v2/users/by_username/new_kaizen_id', headers=headers)
- Fetch by a specific user field
>>> response = requests.get('https://api.kaizenep.com/v2/users/by_field/id2/opt2', headers=headers)
The responses of these calls are identical and have the following structure.
>>> response.json()
{
"id": "new_profile_id",
"user": "new_kaizen_id",
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@org.com",
"roles": ["roleid1"],
"userFields": [
{
"_id": "id1",
"value": "Users value for field 1",
"label": "Person College ID"
},
{
"_id": "id2",
"value": "opt2",
"label": "Gender",
"text_values": ["Female"]
}
],
"auditLog": [
{
"action": "user_created",
"actor": "fry",
"date": "2019-12-05T13:58:07.850768+00:00",
}
]
}
The response contains all information about requested user profile. In some cases it is not required to fetch all the data. This can be controlled by an “includeParts” argument with following options:
all - Include all parts (excluding allRelations)
userFields - Include all user fields
auditlog - Include audit/changes log
roles - Include roles and whether user is an organisation admin
relations - Includes active relations and associated users
allRelations - Includes relations and associated users including past and future entries
includeParts argument is a list and can be passed as a comma-separated query string. For example
>>> response = requests.get('https://api.kaizenep.com/v2/users/by_username/new_kaizen_id?includeParts=roles,auditlog', headers=headers)
>>> response.json()
>>> response
{
"id": "new_profile_id",
"rev": "1-revision-number",
"user": "new_kaizen_id",
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@org.com",
"roles": ["roleid1"],
"auditLog": [
{
"action": "user_created",
"actor": "fry",
"date": "2019-12-05T13:58:07.850768+00:00",
}
]
}
Updating a profile is similar to creation. The main difference is that it is required to include profile id (user id) and a revision number. Revision number is important because it ensures that we are updating up to date version of the profile, this means that it has not been modified by someone else in the meantime. This implies that the profile to update has to be fetched from the server before we can construct the request payload.
All the fields in the payload are optional so that it is possible to update only part of the profile without affecting all other fields.
It’s also possible to update the profile picture. The picture is sent as a base64 encoded string and the content type is required.
An example of a payload is below:
>>> payload = {
"_id": "profile_id",
"_rev": "revision_number",
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@org.com",
"roles": ["roleid1"],
"userFields": [
{"_id": "id1", "value": "Users value for field 1"},
{"_id": "id2", "value": "opt2"}
],
'profilePicture': dict(
content=base64.b64encode(image_file.read()).decode("utf-8"),
contentType='png'
)
}
>>> response = requests.post('https://api.kaizenep.com/v2/users/profile_id', json=payload, headers=headers)
>>> response.json()
{"id": "profile_id", "rev": "new revision number", "username": "kaizen_id"}
Searching for users is done by sending a POST request to the search endpoint. The actual search is controlled by the payload which consts of these parts (all are optional):
filter - This defines the filter applied. For a complete list of available options please look at the OpenApi specification. This does not have and defaults so omitting all filters means it returns everyone.
sort - This defines how the results are sorted. Please have a look at the specification which sortings are available
size - This defines how many rows should be returned. At the moment there is no hard limit. The recommended size depends on requested data being returned. If full objects are being returned the size should be less than 1000.
start - This defines the offset when search allowing for pagination.
options This controls whether it returns only user ids or the full objects. If the full objects are returned is it possible to further limit the content using the includeParts as when retrieving one user.
An example request returning only ids:
>>> payload = {
"filter": {
"state": ["active"]
},
"size": 10,
"options": {
"includeIds": true,
"includeDocs": false
"includeParts": []
}
}
>>> response = requests.post('https://api.kaizenep.com/v2/users/search', json=payload, headers=headers)
>>> response.json()
{
'ids': [
'profile_org_fry_d84ab1e9-b453-4928-8877-66a4f596a911',
'profile_org_fry_660ad117-01b7-4c61-807c-a418093013ad',
'profile_org_fry_e5dca22e-64dd-4a97-b827-27b47111cf58',
'profile_org_fry_8ed2827f-9eeb-43d4-8024-79be7f8b273c',
'profile_org_fry_74a559a6-d248-4761-bd4e-d678fc893948',
'profile_org_fry_28611379-80ba-4d45-806a-139abc5ef85f',
'profile_org_fry_b9a46329-fed4-4208-a2ed-429d344b1487',
'profile_org_fry_72d040ab-57a0-45be-b59e-197f42dad7b6',
'profile_org_fry_509afaea-5d2e-46d6-ae44-c415b0301f22'
],
'size': 10,
'start': 0,
'total': 90
}
An example request returning evaluated profiles:
>>> payload = {
"filter": {
"state": ["active"]
},
"size": 10,
"options": {
"includeIds": false,
"includeDocs": true
"includeParts": []
}
}
>>> response = requests.post('https://api.kaizenep.com/v2/users/search', json=payload, headers=headers)
>>> response.json()
{
'docs': [
{
'createdDate': '2019-01-30',
'email': '',
'firstName': '',
'id': 'profile_org_fry_d84ab1e9-b453-4928-8877-66a4f596a911',
'lastName': '',
'organisation': 'org_fry',
'rev': '1-96bb8305c88e97fb2e996980fee0b9fa',
'state': 'active',
'type': 'user',
'user': 'd84ab1e9-b453-4928-8877-66a4f596a911'
},
...
],
'size': 10,
'start': 0,
'total': 90
}
When there is a set of known profile ids it is possible to retrieve them in bulk using the user fetch method:
>>> payload = {
"ids": [
'profile_org_fry_d84ab1e9-b453-4928-8877-66a4f596a911',
'profile_org_fry_660ad117-01b7-4c61-807c-a418093013ad',
'profile_org_fry_e5dca22e-64dd-4a97-b827-27b47111cf58',
'profile_org_fry_8ed2827f-9eeb-43d4-8024-79be7f8b273c',
'profile_org_fry_74a559a6-d248-4761-bd4e-d678fc893948',
'profile_org_fry_28611379-80ba-4d45-806a-139abc5ef85f',
'profile_org_fry_b9a46329-fed4-4208-a2ed-429d344b1487',
'profile_org_fry_72d040ab-57a0-45be-b59e-197f42dad7b6',
'profile_org_fry_509afaea-5d2e-46d6-ae44-c415b0301f22'
],
"options": {
"includeParts": ["userField]
}
}
>>> response = requests.post('https://api.kaizenep.com/v2/users/fetch', json=payload, headers=headers)
>>> response.json()
{
'docs': [
{
'createdDate': '2019-01-30',
'email': '',
'firstName': '',
'id': 'profile_org_fry_d84ab1e9-b453-4928-8877-66a4f596a911',
'lastName': '',
'organisation': 'org_fry',
'rev': '1-96bb8305c88e97fb2e996980fee0b9fa',
'state': 'active',
'type': 'user',
'user': 'd84ab1e9-b453-4928-8877-66a4f596a911'
},
...
]
}
Hint
Best practices
When retrieving large set of data it is recommended to separate the search and fetch into multiple calls. First run the search and let it return the ids - if only ids are returned the size can be in thousands. Next paginate the resulting ids and fetch evaulated objects iteratively. This ensures a fixed result set, faster responses and ability to meausre progress.