Managing Former Employees via the System Settings REST API

Suppose HR notifies you that five people will be leaving at the end of the month. When you receive notice of a departure, you set the User to [Will be deleted in the future], and delete them after the departure date has passed. This article explains how to carry out these two stages with the REST API.

The difference between deleting and using [Will be deleted in the future] comes down to two points: whether it can be undone, and whether in-progress Cases can still be processed. [Will be deleted in the future] can be undone by unchecking the box, and because the User remains, in-progress Cases can continue to be processed as they are. A deleted User cannot be restored under the same ID, and when a Case advances to a Step that references that User, execution may fail. In either case, Apps that reference that User, for example in the Step Assignee settings, will show an App Settings Error.

Between setting [Will be deleted in the future] and deletion, ask the App Administrator to fix the App settings, and hand over the tasks the User holds. The on-screen procedure is explained in How to Safely Delete Users Without Affecting Apps.

Both setting [Will be deleted in the future] and deleting can be done for each User from [System Settings] > [User List]. The REST API is worth using when this work is repeated every month. If you set things up so that the CSV of departing employees sent from the HR system can be used as is, then from the next time you only need to swap the CSV and run it.

The code examples are in Python 3 and use the Requests package to send HTTP requests. They are split into two scripts, one that sets [Will be deleted in the future] and one that actually deletes, and both are written following the “Preparing to Call the API” code. “Complete Code” at the end gathers each of the two scripts together.

Preparing the CSV

The CSV of departing employees only needs an email address column. Prepare it in the following form and save it as leavers.csv.

email
dylan.thomas@example.com
emily.dickinson@example.com

To find a User with the API, use the email address, because User names can be duplicated when two people share the same name.

This CSV is used both when setting [Will be deleted in the future] and when deleting. Prepare it when you receive notice of the departure, and once the departure date has passed, use the same one again.

Preparing to Call the API

The executing User needs User Manager authorization and permission for API access via Basic authentication. The setup steps and how to look up the API password are the same as in “Preparing to Call the API” in “Reflecting Hires and Transfers with the System Settings REST API.”

Prepare the connection target and credentials as follows.

import csv
import requests
from requests.auth import HTTPBasicAuth

BASE = 'https://example.questetra.net'
AUTH = HTTPBasicAuth('admin@example.com', '{API Password}')

Route API calls through the following functions. When a call fails, it prints the response body and then stops. How to read the response body is also explained in “Preparing to Call the API” in the same article.

def check(r):
    if not r.ok:
        print(r.status_code, r.text)
        r.raise_for_status()
    return r


def call(method, path, **kwargs):
    return check(requests.request(method, f'{BASE}{path}', auth=AUTH, **kwargs))

When Notified of a Departure: Setting [Will be deleted in the future]

Before running this, tell the App Administrator that App Settings Errors will appear. As soon as a User is set to [Will be deleted in the future], Apps that reference that User will show an App Settings Error.

[Will be deleted in the future] is set with deletedInFuture of /API/UGA/Quser/update. Because this API requires a User ID, look up the ID from the email address and then update.

with open('leavers.csv', encoding='utf-8') as f:
    for row in csv.DictReader(f):
        found = requests.get(f'{BASE}/API/User/Quser/find', auth=AUTH,
                             params={'email': row['email']})
        if found.status_code != 400:
            check(found)
        if not found.ok:
            print(row['email'], 'Not found')
            continue
        call('POST', '/API/UGA/Quser/update',
             data={'id': found.json()['quser']['id'], 'deletedInFuture': 'true'})
        print(row['email'], 'Set to Will be deleted in the future')

find returns 400 for an unregistered email address. Only that specific case is treated as “not found,” and other errors such as authentication errors are stopped during the check phase. This is why call is not used here.

update does not change items that are not specified. Here only deletedInFuture is passed, so the User name, email address, and Organization remain as they are.

After the Departure Date: Deleting from the CSV

Deletion is also performed from the same CSV used when setting [Will be deleted in the future]. The workflow platform does not hold departure dates. If you narrow the list by [Will be deleted in the future] and delete, you will also delete people whose departure date has not yet arrived, and Users that another person set to [Will be deleted in the future] for a different reason. Using that month’s CSV of departing employees keeps you from targeting the wrong Users.

with open('leavers.csv', encoding='utf-8') as f:
    for row in csv.DictReader(f):
        found = requests.get(f'{BASE}/API/User/Quser/find', auth=AUTH,
                             params={'email': row['email']})
        if found.status_code != 400:
            check(found)
        if not found.ok:
            print(row['email'], 'Not found')
            continue
        quser = found.json()['quser']
        if not quser['deletedInFuture']:
            print(row['email'], 'Not set to Will be deleted in the future')
            continue
        call('POST', '/API/UGA/Quser/delete', data={'id': quser['id']})
        print(row['email'], 'Deleted')

A deleted User cannot be restored. Run this only after the App settings have been fixed and the remaining tasks have been handed over.

Users who are not set to [Will be deleted in the future] are not deleted, but are printed out instead. This is because people who skipped the [Will be deleted in the future] step, or whose setting was once canceled, may remain in the CSV.

Checking for Leftover Deletions

Users remaining as [Will be deleted in the future] can be checked in a list filtered by deletedInFuture. The API that returns lists has an upper limit on the number of items returned in a single call. Specify the number per call with limit, shift start each time, and repeat until the number returned is less than limit.

LIMIT = 10
start = 0
while True:
    r = call('GET', '/API/User/Quser/list',
             params={'deletedInFuture': 'true', 'start': start, 'limit': LIMIT})
    qusers = r.json()['qusers']
    for u in qusers:
        print(u['id'], u['name'], u['email'])
    if len(qusers) < LIMIT:
        break
    start += LIMIT

This list includes people whose departure date has not yet arrived, as well as Users that another person set to [Will be deleted in the future] for a different reason. You cannot tell from this list whether a User is OK to delete. Use it to cross-check against the CSV of departing employees and see whether anything was left undeleted.

Complete Code

This is the script that sets [Will be deleted in the future]. Before running it, read “How to Proceed with Execution” below.

import csv
import requests
from requests.auth import HTTPBasicAuth

BASE = 'https://example.questetra.net'
AUTH = HTTPBasicAuth('admin@example.com', '{API Password}')


def check(r):
    if not r.ok:
        print(r.status_code, r.text)
        r.raise_for_status()
    return r


def call(method, path, **kwargs):
    return check(requests.request(method, f'{BASE}{path}', auth=AUTH, **kwargs))


with open('leavers.csv', encoding='utf-8') as f:
    for row in csv.DictReader(f):
        found = requests.get(f'{BASE}/API/User/Quser/find', auth=AUTH,
                             params={'email': row['email']})
        if found.status_code != 400:
            check(found)
        if not found.ok:
            print(row['email'], 'Not found')
            continue
        call('POST', '/API/UGA/Quser/update',
             data={'id': found.json()['quser']['id'], 'deletedInFuture': 'true'})
        print(row['email'], 'Set to Will be deleted in the future')

This is the script that deletes.

import csv
import requests
from requests.auth import HTTPBasicAuth

BASE = 'https://example.questetra.net'
AUTH = HTTPBasicAuth('admin@example.com', '{API Password}')


def check(r):
    if not r.ok:
        print(r.status_code, r.text)
        r.raise_for_status()
    return r


def call(method, path, **kwargs):
    return check(requests.request(method, f'{BASE}{path}', auth=AUTH, **kwargs))


with open('leavers.csv', encoding='utf-8') as f:
    for row in csv.DictReader(f):
        found = requests.get(f'{BASE}/API/User/Quser/find', auth=AUTH,
                             params={'email': row['email']})
        if found.status_code != 400:
            check(found)
        if not found.ok:
            print(row['email'], 'Not found')
            continue
        quser = found.json()['quser']
        if not quser['deletedInFuture']:
            print(row['email'], 'Not set to Will be deleted in the future')
            continue
        call('POST', '/API/UGA/Quser/delete', data={'id': quser['id']})
        print(row['email'], 'Deleted')

How to Proceed with Execution

First, run the script with a CSV containing only one row, and confirm in [System Settings] > [User List] that the User has been set to [Will be deleted in the future]. POST APIs rewrite the workflow platform’s data on the spot. Once you have confirmed this, run the remaining rows.

Try the deletion script with a one-row CSV as well. Because deletion cannot be undone, confirm that this one person’s departure date has passed, and that the App settings fixes and task handover are complete, before running it.

Related Pages

Discover more from Questetra Support

Subscribe now to keep reading and get access to the full archive.

Continue reading