

Suppose that due to an organizational restructuring in April, changes to the Organization structure are to be made as follows:
- Establish a new Western Japan Sales Section under the Sales Department
- Rename the Quality Assurance Office under the Development Department to Quality Management Department and move it directly under the company
- Abolish the Information Systems Office under the General Affairs Department
In this article, we will explain how to reflect new additions, renaming, parent Organization changes, and updating the members belonging to each Organization via REST API. The abolition of Organizations is covered in “Abolishing Organizations via System Settings REST API” (hereinafter, the Organization abolition article).
Adding and editing Organizations can also be done via [System Settings] > [Organization List]. By using [Bulk Organization Registration], you can register up to 300 Organizations at once from a CSV file. If it is a one-time Task, that approach is easier.
Using the REST API is suitable when this Task is repeated every year. If you set it up so that the organizational chart exported from your HR system can be fed directly, next time onward you only need to replace the CSV file and run the script.
Members are updated via the roster for each organization. In an organizational restructuring, members belonging to a former Organization do not necessarily move as a whole to a new Organization, and some members may move to different departments. If you prepare and replace updated rosters for each Organization after restructuring, the procedure remains the same regardless of how members transfer. User registration and individual transfers are explained in “Reflecting New Hires and Transfers via System Settings REST API”.
The sample code is written in Python 3 and uses the Requests package to send HTTP requests. There is a single script that connects to the destination settings, retrieves the Organization list, reads and cross-checks all three CSV files (rename.csv, orgchart.csv, and roster.csv), and if there are no issues, sequentially applies the changes. The full script is available under “Full Code”.
5 Things That Happen to Organizations
What happens to Organizations during a restructuring can be divided into the following 5 categories. Depending on which event occurs, which CSV file to prepare, the API to use, and whether the Organization ID changes will differ.
| What Happens to the Organization | CSV to Write | API to Use | Organization ID |
|---|---|---|---|
| A new Organization is added | orgchart.csv | Qgroup/add | Newly assigned |
| Position in the Organization tree changes | orgchart.csv | Qgroup/update (parentQgroupId) | Unchanged |
| Position remains, Organization name changes | rename.csv | Qgroup/update (name) | Unchanged |
| Both position and Organization name change | rename.csv and orgchart.csv | Qgroup/update (name and parentQgroupId) | Unchanged |
| Organization is abolished | Organization abolition article | Organization abolition article | — |
In the opening example, the Western Japan Sales Section corresponds to row 1 of the table, the Quality Assurance Office to row 4, and the Information Systems Office to row 5. Abolishing row 5 alone consists of two steps: setting [Will be deleted in the future] and deleting it on the deletion date, which cannot be undone, so it is explained in the Organization abolition article.
Member replacements are reflected separately from these 5 events using the roster per Organization in roster.csv.
There is another approach for the 3 rows where positions or Organization names change. That is to add a new Organization and abolish the former Organization. The organizational chart will take the same structure, but the Organization ID will be different. Which approach to choose should be decided in the next section – “Modifying Existing Organizations vs. Creating New Organizations” – before writing the CSV files.
Modifying Existing Organizations vs. Creating New Organizations
The selection criteria consist of the following 3 points:
- Visibility of historical records. If you modify an existing Organization, the Organization ID remains unchanged, so Organizations recorded in already completed Cases will be displayed with the new Organization name and position. If you create a new Organization, historical records will remain associated with the former Organization. Decide based on whether you want to preserve the pre- and post-restructuring states as separate Organizations.
- In-progress Cases and App settings. If you modify an existing Organization, in-progress Cases assigned to that Organization, values stored in Organization-type Data Items, and App settings referencing that Organization will be carried over automatically. If you create a new Organization, App settings pointing to the former Organization must be updated to the new Organization. In-progress Cases already assigned to the former Organization will continue to be processed under the former Organization.
- Workload. Modifying existing Organizations requires only
rename.csvandorgchart.csv. Creating new Organizations adds the two-step process described in the Organization abolition article. For Organizations that have child Organizations, the workload difference becomes even larger. If you modify existing Organizations, child Organizations remain underneath them as-is, but if you create new Organizations, lower-level Organizations must also be recreated.
When creating a new Organization, the main concern is App settings referencing the former Organization. In addition to requesting each App Administrator to update them, you can also update them in bulk as described in “Bulk Reassignment of Operator Settings” in the Organization abolition article.
Although it requires more effort, creating a new Organization is arguably the safer approach. This is because it does not affect active Cases. Replacing members of an existing Organization does not change assignees for Tasks already generated in Apps designating that Organization as the Operator, but assignees will change starting from Tasks generated after the replacement. Active Cases are no exception. While this may be desirable in some situations, it is generally an operation to be handled with caution.
If you choose to modify existing Organizations and there are no Organizations to abolish, the Task can be completed entirely within this article. If you choose to create new Organizations or if there are Organizations to abolish, the Tasks in the Organization abolition article will follow after this script.
If you choose to create new Organizations, the following 2 points apply to writing CSV files:
- If giving a new Organization the same name as the former Organization, first rename the former Organization to something like
Sales Dept (Old). Record this renaming step inrename.csvas well. - Write rosters for new Organizations in
roster.csv, and omit former Organizations. Even when moving members to a new Organization, keep the roster for the former Organization intact. This prevents Apps designating the former Organization as Operators from lacking assignees until the deletion date. Between placing the former Organization under [Will be deleted in the future] and its deletion, App Administrators will update Operator settings to the new Organization. That workflow is explained in the Organization abolition article.
Preparing CSV Files
Prepare three separate CSV files for Renaming, Organizational Chart, and Roster.
For Organizations to be renamed, place former Organization names and new Organization names side by side and save as rename.csv.
old_name,new_name
Quality Assurance Office,Quality Management DepartmentFor the new organizational chart, place Organization names and parent Organization names side by side and save as orgchart.csv. Since name changes are reflected immediately, Organization names should be written using their updated (post-renaming) forms.
name,parent
Sales Department,Sample Corporation
Western Japan Sales Section,Sales Department
Quality Management Department,Sample CorporationIn parent, write the name of the parent Organization. When creating a department under the top-level Organization (the Organization representing the entire company), write the Organization name displayed at the top of [System Settings] > [Organization List]. If you are making a newly established Organization a parent Organization, write the row that becomes the parent first.

In the organizational chart CSV, you can include unchanged Organizations in addition to changed ones. In the example above, the Sales Department is an unchanged Organization. The script searches for Organizations in each row by Organization name and handles them as follows:
- If not found, it creates it as a new Organization.
- If found and the parent Organization differs, it updates the parent reference.
- If found and the parent Organization is identical, it does nothing.
For every row, executing multiple times will produce the same result. Therefore, an organizational chart exported from an HR system can be used directly without filtering out unchanged Organizations. If there is a row where the parent Organization specified in parent is not found, no new Organizations will be created and execution will stop without applying any rows.
Organizations omitted from the organizational chart CSV will remain as-is. Whether to abolish them is specified separately in the Organization abolition article. This is because treating absence from the organizational chart as abolition would result in unintentionally deleting accidentally omitted Organizations. If an Organization to be abolished has child Organizations and those child Organizations are to be retained, update the parent Organization of the child Organizations in this CSV beforehand, as Organizations with child Organizations cannot be deleted.
Save the roster after restructuring as roster.csv. The first row is the header row, which the script skips. From the second row onward, write one Organization per row, followed by the email addresses of Users belonging to that Organization after the Organization name. Organization names, like in orgchart.csv, should be written using names after renaming. Writing the same Organization across 2 or more rows will cause execution to stop during cross-checking.
qgroup,emails
Sales Department,taro.tanaka@example.com
Western Japan Sales Section,hanako.suzuki@example.com,jiro.sato@example.comAffiliations for Organizations listed in the roster will be replaced with the Users specified in that row. Please include everyone belonging to the Organization after restructuring, including members who remain in that Organization. Users omitted from the row will be removed from that Organization, and rows without any email addresses will set the Organization’s membership count to 0. If a User belongs to multiple Organizations, list them in the rows for each respective Organization. Organizations whose affiliations do not change will remain unchanged if omitted from the roster.
Users specified in the roster must already be registered. Users joining the company should be registered beforehand following the procedures in “Reflecting New Hires and Transfers via System Settings REST API”.
If there are Users who belong solely to an Organization being abolished, include those Users in the row for their destination Organization. Deleting an Organization leaves its members unassigned to any Organization.
Preparing for API Calls
The User performing the action requires User Management privileges and permission for API access via Basic authentication. The setup procedures, API password lookup, preparation of destination and authentication information, and the two helper functions check and call wrapping API calls are identical to “Preparing for API Calls” in “Reflecting New Hires and Transfers via System Settings REST API”. The code in this article also utilizes those check and call functions.
The script operates with User Management privileges alone. System Administration privileges are required only if using [Apps referencing this Organization] during the post-Task “Reviewing App Settings”.
Retrieving the Organization List
The API utilizes Organization IDs, whereas the CSV files contain Organization names. First, retrieve the Organization list to build a dictionary mapping Organization names to Organization details. Because Organization names are unique within the workflow infrastructure, lookups can be performed by Organization name. Repeatedly calling /API/User/Qgroup/list with limit and start follows the same approach as “Retrieving the Organization List” in “Reflecting New Hires and Transfers via System Settings REST API”.
Store the entire response object per item in the dictionary, not just the Organization ID. groups['Quality Assurance Office']['id'] is the Organization ID, and groups['Quality Assurance Office']['parentQgroupId'] is the parent Organization ID. These are used to determine whether a parent Organization change is required.
Cross-checking: Verifying Organizations and Users
Before modifying Organizations or affiliations, read and cross-check all three CSV files completely. Collect User IDs listed in the roster here as well. This allows executing renames, organizational chart updates, and roster replacements sequentially without inserting User searches in between. Issues found during cross-checking are accumulated in errors and displayed together at the end.
with open('rename.csv', encoding='utf-8') as f:
renames = list(csv.DictReader(f))
with open('orgchart.csv', encoding='utf-8') as f:
orgchart = list(csv.DictReader(f))
with open('roster.csv', encoding='utf-8') as f:
reader = csv.reader(f)
next(reader)
rows = [(row[0], [email for email in row[1:] if email])
for row in reader if row]
errors = []The matching process is performed against known, which collects Organization names from the Organization list. As organization names change or new organizations are added, known is updated accordingly; the organizational chart and the personnel roster are then verified using these updated or newly established names. The groups dictionary—created from the master list—remains unchanged during this phase and is instead utilized during the “Applying Changes” stage.
For name changes, any entry where the old Organization name is not present in known is flagged and halted. However, if an organization with the new name already exists, the entry is permitted to proceed, as it is considered already renamed. It is necessary to halt any entries where neither the old nor the new name exists in known; otherwise, the subsequent “Applying Changes” step would incorrectly create a new organization using the updated name.
Entries where the new Organization name matches an existing Organization name are also flagged and halted. This is because Organization names must be unique within the workflow infrastructure, and the API for renaming Organizations would otherwise return an error. An exception is made if the name becomes available due to a renaming operation that occurred in an earlier row of the same CSV file.
known = set(groups)
for row in renames:
if row['old_name'] in known:
if row['new_name'] in known:
errors.append((row['new_name'], 'Organization name already exists'))
known.discard(row['old_name'])
known.add(row['new_name'])
elif row['new_name'] not in known:
errors.append((row['old_name'], 'Not found in Organization list'))
For the organizational chart, verify whether parent Organizations can be found. Parent Organizations are treated as found if they exist in the post-rename Organization list or if they appear as a name in an earlier row within the same CSV. Causes for missing parent Organizations include parent Organizations appearing in later rows, or naming discrepancies between the HR system and the workflow infrastructure. Output both the row’s Organization name and parent Organization name for troubleshooting.
for row in orgchart:
if row['parent'] not in known:
errors.append((row['name'], 'Parent Organization not found:', row['parent']))
known.add(row['name'])For rosters, verify that all Organizations and Users are found, and that no Organization appears in 2 or more rows. Search for Organizations including newly created ones in the organizational chart. Search for Users by email address, as Users may share duplicate names. Store found User IDs in roster for each Organization, and pass them directly during “Replacing Member Affiliations”. /API/User/Quser/find returns 400 for unregistered email addresses. this specific case is treated as “not found,” while other issues—such as authentication errors—are caught and handled during the validation (check) phase.
names = [name for name, _ in rows]
for name in sorted({name for name in names if names.count(name) > 1}):
errors.append((name, 'Appears in 2 or more rows'))
roster = {}
for name, emails in rows:
if name not in known:
errors.append((name, 'Organization not found'))
memberships = []
for email in emails:
found = requests.get(f'{BASE}/API/User/Quser/find', auth=AUTH,
params={'email': email})
if found.status_code != 400:
check(found)
if not found.ok:
errors.append((email, 'User not found'))
continue
memberships.append({'quserId': found.json()['quser']['id']})
roster[name] = memberships
If even a single error exists, execution stops without applying any changes. This avoids leaving the system in a partially updated state where tracking progress becomes unclear.
if errors:
for error in errors:
print(*error)
raise SystemExit('No changes were applied')
Renaming: Changing Organization Names
The API to update Organizations is /API/UGA/Qgroup/update. id is required, and unspecified parameters remain unchanged. Passing only the Organization name leaves parent Organizations, memberships, and Organization IDs intact.
for row in renames:
old = groups.get(row['old_name'])
if not old:
print(row['new_name'], 'Already renamed')
continue
call('POST', '/API/UGA/Qgroup/update',
data={'id': old['id'], 'name': row['new_name']})
groups[row['new_name']] = groups.pop(row['old_name'])
print(row['old_name'], '→', row['new_name'])
Update keys in the groups dictionary to new Organization names as well. This ensures subsequent sections can locate Organizations using post-rename names.
New Additions and Position Changes: Applying Organizational Chart
The API to add Organizations is /API/UGA/Qgroup/add, where name and parentQgroupId are required. The response parameter qgroup contains details of the added Organization. To update parent Organizations, pass id and parentQgroupId to /API/UGA/Qgroup/update.
The following function searches for an Organization by name in a dictionary; if it is not found, it adds it, and if the parent Organization differs, it updates the parent. Newly added Organizations are also stored in the dictionary so that subsequent rows in the same CSV file can reference them as a parent Organization. No users are assigned to newly created Organizations. For Organizations whose parent has been changed, the existing members remain in place. Member affiliations for both cases are updated in the subsequent section “Replacing Member Affiliations”.
def ensure_group(name, parent_id):
group = groups.get(name)
if not group:
r = call('POST', '/API/UGA/Qgroup/add',
data={'name': name, 'parentQgroupId': parent_id})
groups[name] = r.json()['qgroup']
return 'Created as new Organization'
if group['parentQgroupId'] != parent_id:
call('POST', '/API/UGA/Qgroup/update',
data={'id': group['id'], 'parentQgroupId': parent_id})
group['parentQgroupId'] = parent_id
return 'Updated parent Organization'
return 'No changes'
for row in orgchart:
print(row['name'], ensure_group(row['name'], groups[row['parent']]['id']))
Replacing Member Affiliations
The API to set assigned Users for an Organization in bulk is /API/UGA/Membership/set. Passing qgroupId and a list of Users belonging to that Organization replaces membership for the specified Organization with the provided list.
Pass post-restructuring rosters for each Organization. Each row in roster.csv corresponds to one API call.
Primary Organization affiliations are not specified in this API, but may change. A User added via roster who previously held no Organization affiliations will have that Organization set as their primary Organization. If they already belonged to another Organization, their primary Organization remains unchanged. Users removed from a roster whose primary Organization was that Organization will be reassigned to another Organization. If they belong to no other Organizations, they become unassigned, and their primary Organization designation is removed. Verify primary Organization settings for removed Users after script execution.
for name, memberships in roster.items():
call('POST', '/API/UGA/Membership/set',
json={'qgroupId': groups[name]['id'], 'memberships': memberships})
print(name, f'Updated membership count to {len(memberships)}')
Task Order
The script cross-checks all three CSV files before applying changes in the following order:
rename.csv. Applies renaming. Processed first so that subsequentorgchart.csventries can use post-rename names.orgchart.csv. Applies new additions and parent Organization updates. All destination Organizations become available at this stage.roster.csv. Applies post-restructuring rosters for each Organization.
Announce to all users once execution is finished. Details are provided under “Reviewing App Settings”.
When abolishing Organizations, perform this script, issue company-wide announcements, and configure [Will be deleted in the future] in the Organization abolition article in that order on the same day. Include deletion dates in announcements.
Full Code
This script processes the three CSV files sequentially. Please read “Execution Guidelines” below before running the script.
import csv
import requests
from requests.auth import HTTPBasicAuth
BASE = 'https://example.questetra.net'
AUTH = HTTPBasicAuth('admin@example.com', '{API Password}')
LIMIT = 10
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))
groups = {}
start = 0
while True:
r = call('GET', '/API/User/Qgroup/list',
params={'start': start, 'limit': LIMIT})
qgroups = r.json()['qgroups']
groups.update({g['name']: g for g in qgroups})
if len(qgroups) < LIMIT:
break
start += LIMIT
with open('rename.csv', encoding='utf-8') as f:
renames = list(csv.DictReader(f))
with open('orgchart.csv', encoding='utf-8') as f:
orgchart = list(csv.DictReader(f))
with open('roster.csv', encoding='utf-8') as f:
reader = csv.reader(f)
next(reader)
rows = [(row[0], [email for email in row[1:] if email])
for row in reader if row]
errors = []
known = set(groups)
for row in renames:
if row['old_name'] in known:
if row['new_name'] in known:
errors.append((row['new_name'], 'Organization name already exists'))
known.discard(row['old_name'])
known.add(row['new_name'])
elif row['new_name'] not in known:
errors.append((row['old_name'], 'Not found in Organization list'))
for row in orgchart:
if row['parent'] not in known:
errors.append((row['name'], 'Parent Organization not found:', row['parent']))
known.add(row['name'])
names = [name for name, _ in rows]
for name in sorted({name for name in names if names.count(name) > 1}):
errors.append((name, 'Appears in 2 or more rows'))
roster = {}
for name, emails in rows:
if name not in known:
errors.append((name, 'Organization not found'))
memberships = []
for email in emails:
found = requests.get(f'{BASE}/API/User/Quser/find', auth=AUTH,
params={'email': email})
if found.status_code != 400:
check(found)
if not found.ok:
errors.append((email, 'User not found'))
continue
memberships.append({'quserId': found.json()['quser']['id']})
roster[name] = memberships
if errors:
for error in errors:
print(*error)
raise SystemExit('No changes were applied')
for row in renames:
old = groups.get(row['old_name'])
if not old:
print(row['new_name'], 'Already renamed')
continue
call('POST', '/API/UGA/Qgroup/update',
data={'id': old['id'], 'name': row['new_name']})
groups[row['new_name']] = groups.pop(row['old_name'])
print(row['old_name'], '→', row['new_name'])
def ensure_group(name, parent_id):
group = groups.get(name)
if not group:
r = call('POST', '/API/UGA/Qgroup/add',
data={'name': name, 'parentQgroupId': parent_id})
groups[name] = r.json()['qgroup']
return 'Created as new Organization'
if group['parentQgroupId'] != parent_id:
call('POST', '/API/UGA/Qgroup/update',
data={'id': group['id'], 'parentQgroupId': parent_id})
group['parentQgroupId'] = parent_id
return 'Updated parent Organization'
return 'No changes'
for row in orgchart:
print(row['name'], ensure_group(row['name'], groups[row['parent']]['id']))
for name, memberships in roster.items():
call('POST', '/API/UGA/Membership/set',
json={'qgroupId': groups[name]['id'], 'memberships': memberships})
print(name, f'Updated membership count to {len(memberships)}')
Execution Guidelines
POST APIs modify workflow infrastructure data immediately on execution. There is no test run mechanism, and executing any CSV modifies production data upon invocation. If you wish to verify behavior beforehand, test on a non-production workflow infrastructure environment.
Renaming operations can be run incrementally in batches. Keep only header rows in orgchart.csv and roster.csv, and execute while incrementally adding rows to rename.csv. Already renamed rows will be skipped, allowing previously executed rows to remain in the file. Because the script opens all three CSV files, missing files cause execution to halt. Header-only CSVs proceed without applying changes. Verify Organization names in [System Settings] > [Organization List] after each run.
Apply organizational chart updates and membership replacements in a single complete run rather than incrementally. Each update to organizational charts or affiliations triggers recalculations for every User’s “Start New” list. If renames were executed earlier, keep rename.csv intact and populate orgchart.csv and roster.csv with full intended contents prior to running.
Changes reflect differently across screens. Updates appear immediately in [System Settings] > [Organization List]. Conversely, each User’s “Start New” list updates after a delay of several minutes, and may not reflect changes immediately upon script completion.
If execution halts mid-process, re-run the script from the beginning. If stopped during cross-checking, resolve identified CSV issues; since no data was modified, re-running is safe. If stopped mid-application, re-running from the start is also safe: already renamed rows and unchanged Organizations are skipped, and rosters are overwritten with identical contents, yielding unchanged results for previously executed rows.
Upon completion, issue a company-wide announcement following the content described in “Reviewing App Settings”.
Reviewing App Settings
If you modified existing Organizations, App settings do not require updates. Because Organization IDs remain unchanged, Organization references configured in Apps remain valid. If you created new Organizations, request App Administrators to update settings referencing former Organizations following the procedures in the Organization abolition article.
However, even if App settings do not require updates, Operators may not function as originally intended. The following 3 scenarios will not trigger errors nor notify App Administrators. Because executing personnel cannot easily inspect every App, issue an announcement after completion requesting App Administrators to review their Apps. Individually contacting relevant App Administrators following major organizational changes is generally impractical.
- Operator settings configured with [Members who belong to sub organizations]. Changing parent Organizations alters included child Organizations. Assignees will change both in the original parent location and the new parent location.
- Newly created Organizations. Creating an Organization does not assign it to any App automatically. Designating newly created Organizations as Operators requires configuration on the App side.
- Organizations with replaced members. In Apps designating that Organization as Operators, assignees for Tasks generated after member replacement will change.
In announcements, communicate the completion of organizational updates alongside requests to review these 3 points. If abolishing Organizations, include details regarding Organization deletions and scheduled deletion dates. Content regarding abolition is detailed under “Configuring [Will be deleted in the future]” in the Organization abolition article.
Users with System Administration privileges can inspect which Apps reference specific Organizations. Open an Organization in [System Settings] > [Organization List] and check [Apps referring to the organization]. This button is hidden for Users holding only User Management privileges. The inspection target is the latest active App version (running version); draft versions under edit will not appear in this list.




