Admin API
The Admin API provides interfaces for user, profile, group, organisation, and permission management. In addition, templates and snippets (text modules) can be queried and managed. The endpoints are exposed as a versioned REST API under /api/v3/{datasourceId}/Admin/.... Packages are exported and imported in the Web Admin.
Authentication
In order to call the Admin APIs, a corresponding client must be registered in the primedocs.config, see primedocs.config.
After registration, an AccessToken can be requested from the IdS.
This PowerShell example shows how to obtain the AccessToken:
# Configuration
$datasourceId = "b78c3707-d7c7-4fc7-b97f-87d70f63c1ac"
$tokenUrl = "https://{instanz}/ids/connect/token"
$clientID = "CustomApiClient"
$clientSecret = "CustomClient_Secret_123"
$scope = "pd_AdminWebApi"
$tokenRequestHeaders = @{
"Content-Type" = "application/x-www-form-urlencoded"
}
$tokenRequestBody = @{
client_id = $clientID
client_secret = $clientSecret
grant_type = "client_credentials"
scope = $scope
}
# Request the access token
try {
$tokenResponse = Invoke-RestMethod -Uri $tokenUrl -Method POST -Headers $tokenRequestHeaders -Body $tokenRequestBody
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
if (-not $tokenResponse.access_token) {
Write-Error "Failed to obtain an access token!"
exit 1
}
# Access token information
Write-Host "Access Token: $($tokenResponse.access_token)"
$accessToken = $tokenResponse.access_token
Roles of the API client
The Admin API checks an API client against the same roles as a signed-in administrator. Which roles the client has is set by the roles attribute of its registration in primedocs.config, see primedocs.config: Admin API. Without the attribute the client receives the default roles OrganisationAdmin, UserAdmin, TemplateAdmin, CampaignAdmin and SnippetAdmin, that is neither SysAdmin nor Developer.
| Area | Required role (one of them) |
|---|---|
| Users, groups, app user groups | SysAdmin, UserAdmin |
| Organisational units including tree, themes and profile roles | SysAdmin, OrganisationAdmin |
| Templates | SysAdmin, TemplateAdmin, CampaignAdmin |
| Snippets and snippet groups | SysAdmin, SnippetAdmin |
Translation keys (/Translations) | SysAdmin, OrganisationAdmin, TemplateAdmin |
Roles (/Roles) and package export and import (/Package) | SysAdmin |
| Statistics, client usage, settings, languages, user synchronisation, connected services, profile share policies | SysAdmin, Developer |
If the required role is missing, the API responds with HTTP 403. The server log of the Web API then names the client ID, the endpoint and the expected roles. Tokens for the Connect API or the MCP server are always rejected by the Admin API. The endpoints that hand out or replace the datasource key or encrypt and decrypt with it are only open to signed-in administrators, not even to a client with roles="SysAdmin".
A client without a roles attribute no longer has access to the areas that require SysAdmin or Developer, for example statistics, client usage and roles. If it needs them, add roles="SysAdmin" to its registration.
Query-Syntax
The Admin API offers typical "CRUD" (Create / Read / Update / Delete) endpoints as an HTTP API.
To search for specific data, the following GET endpoints offer a "query" parameter:
| HTTP GET-Endpoint | Description |
|---|---|
/api/v3/{datasourceId}/Admin/AppUserGroups | Returns a list of "AppUserGroups". |
/api/v3/{datasourceId}/Admin/Groups | Returns a list of "UserGroups". |
/api/v3/{datasourceId}/Admin/Users | Returns a list of "Users". |
/api/v3/{datasourceId}/Admin/Templates | Returns a list of "Templates". |
/api/v3/{datasourceId}/Admin/OrganizationUnits | Returns a list of "OrganizationUnits". |
All of these APIs return paginated lists of data. Filtering/searching can be triggered using the "Query" parameter.
The syntax consists of [Property]:"[filter]" [AdditionalProperty]:"[additional filter]"...
" can be optionally placed around the filter, especially if spaces are part of the filter criteria.
Some properties support the * wildcard operator at the beginning and end.
The individual filters are linked with AND.
Example:
https://{instanz}/webapi/api/v3/{datasourceId}/Admin/Templates?query=localizedName:*brief%20active:true
Searches for all active templates whose name ends with "brief".
AppUserGroups
Possible "Query" options:
title: Search by title- Supports the
*wildcard operator at the beginning and end.
- Supports the
primarySid: Search by primarySid- Supports the
*wildcard operator at the beginning and end.
- Supports the
Example:
title:Admin primarySid:123* = Find all AppUserGroups that have the title "Admin" and start with the primarySid 123.
UserGroups
Possible "Query" options:
title: Search by title- Supports the
*wildcard operator at the beginning and end.
- Supports the
primarySid: Search by primarySid- Supports the
*wildcard operator at the beginning and end.
- Supports the
Example:
title:Admin primarySid:123* = Find all UserGroups that have the title "Admin" and start with the primarySid 123.
Users
Possible "Query" options:
title: Search by title- Supports the
*wildcard operator at the beginning and end.
- Supports the
primarySid: Search by primarySid- Supports the
*wildcard operator at the beginning and end.
- Supports the
field[<User.FieldId>]: Search for a configurable user field (e.g.field[User.FirstName])- Supports the
*wildcard operator at the beginning and end.
- Supports the
Example:
title:"John*" Field[User.EmailField]:"*foobar.com" = Search for all Users whose title begins with "John" and whose email address in the User.EmailField field ends with foobar.com.
Templates
Possible "Query" options:
templateId: Search by TemplateId- Must be a GUID.
metaTemplateId: Search by MetaTemplateId- Must be a GUID.
active: Search for active/inactive (deleted) templates- Must be a Bool (
True,False).
- Must be a Bool (
templatestate: Search for the template status- Supported: 0 (=
InProgress), 1 (=InTest), 2 (=Released)
- Supported: 0 (=
templateGroupId: Search for the parent template group- Must be a GUID.
localizedName: Search by name.- Supports the
*wildcard operator at the beginning and end.
- Supports the
localizedDescription: Search by description.- Supports the
*wildcard operator at the beginning and end.
- Supports the
tags: Search by tags- AND link via
;and OR link via|. (ExampleTAG1;TAG2;TAG3|TAG4)
- AND link via
Example:
active:true tags:ProjectX;Finance|Controlling = Search for all active Templates that are either tagged with ProjectX and Finance or that have the tag Controlling.
OrganizationUnits
Possible "Query" options:
title: Search by title- Supports the
*wildcard operator at the beginning and end.
- Supports the
sid: Search by SID- Supports the
*wildcard operator at the beginning and end.
- Supports the
field[<Org.FieldId>]: Search for a configurable field of the organization unit (e.g.field[Org.Title])- Supports the
*wildcard operator at the beginning and end.
- Supports the
Example:
title:*Sales* sid:OU-123 = Find all OrganizationUnits whose title contains "Sales" and whose SID is OU-123.
Response structure and pagination
The list endpoints return the results in a uniform wrapper: the actual records are under data, the pagination information under pagingDetails.
{
"data": [
{ "id": "…", "title": "…" }
],
"pagingDetails": {
"page": 1,
"pageSize": 10,
"totalPages": 5
}
}
Pagination is controlled via the query parameters page and pageSize:
page: default1.pageSize: default10, minimum10, maximum100(values outside this range are adjusted accordingly).
Endpoint paths
The endpoints are reachable via the route prefix /api/v3/{datasourceId}/Admin/…. In a standard installation the Web API is mounted under the host path /webapi, so the full URL is https://{instance}/webapi/api/v3/{datasourceId}/Admin/…. The PowerShell examples on this page use this full form.
/Admin/ClientUsages provides the evaluation of the clients and versions in use, see Statistics and Client usage.
Common endpoints (/Common)
Alongside /Admin/… there is the route prefix /api/v3/{datasourceId}/Common/… for queries needed by both Admin API and Connect API clients. They deliberately exist only once, so that an existing client does not additionally have to be registered for the other area.
| Endpoint | Description |
|---|---|
GET .../Common/MetaTemplates | Returns all meta template IDs with their name as an object (GUID → name). The IDs are required to search by metaTemplateId, both in the Templates query of the Admin API and in the templateQuery of the Connect API. The Connect query only considers the current template types (see Querying templates). |
GET .../Common/Principals | Searches security accounts (users, groups, app user groups) by name, simultaneously in the primedocs database and in the connected directory (AD, Entra ID, SCIM). Each hit returns name, primarySid, type (User, Group, AppUserGroup), isOnboardedInDataSource and idInDataSource (database ID, if already onboarded). This makes it possible to determine a SID for onboarding or for granting a permission without querying the directory separately. |
Query parameters of .../Common/Principals:
| Parameter | Description |
|---|---|
query (required) | Search term, at least 2 characters. |
limit (optional) | Maximum number of hits. Default 20, maximum 100. |
types (optional) | Comma-separated list of the types to search for: User, Group, AppUserGroup. Default: all three. |
sources (optional) | Comma-separated list of sources: DataSource (primedocs database) and PrincipalConnector (connected directory). Default: both. |
The /Common endpoints accept an access token with the scope pd_AdminWebApi or pd_ConnectWebApi (as well as the Web App token). Obtaining the token is identical to Authentication. Only the path differs. For a client credentials token (service account without user context) the datasource is resolved from the {datasourceId} path. For a user token, that user's datasource permissions apply.
# Prepare headers for request
$apiRequestHeaders = @{
"Authorization" = "Bearer $accessToken"
}
# Query meta template IDs
$apiUrl = "https://{instance}/webapi/api/v3/$($datasourceId)/Common/MetaTemplates"
$metaTemplates = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
$metaTemplates.PSObject.Properties | ForEach-Object { Write-Output "$($_.Name): '$($_.Value)'" }
# Search a security account (users only, directory only)
$apiUrl = "https://{instance}/webapi/api/v3/$($datasourceId)/Common/Principals?query=smith&types=User&sources=PrincipalConnector"
$principals = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
$principals | Format-Table name, primarySid, type, isOnboardedInDataSource
Examples
This AccessToken must be included in the Authorization header for all calls. The API allows, among other things, listing, creating, and editing users, profiles, groups, organizational units, and snippet groups, as well as querying templates and AppUserGroups. Examples can be found below:
List users
This lists existing users:
# Prepare headers for request
$apiRequestHeaders = @{
"Authorization" = "Bearer $accessToken"
}
Write-Host "Getting users..."
$pageSize = Read-Host "Enter pagesize"
$page = Read-Host "Enter page"
try {
$apiUrl = "https://{instanz}/webapi/api/v3/$($datasourceId)/Admin/Users?pageSize=$($pageSize)&page=$($page)"
$response = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
foreach ($user in $response.data) {
Write-Output "$($user.id): '$($user.title)'"
}
Write-Output "Page: $($response.pagingDetails.page) / $($response.pagingDetails.totalPages)"
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
User onboarding
This call can be used to add a new user using their primarySid. The User Onboarding & Offboarding process is then run automatically. Groups are created in the same way via POST .../Admin/Groups?primarySid=…. The former parameter sid is still accepted. If both are missing, the API responds with HTTP 400.
primarySid denotes the SID of a user, group or app user group from the directory. This also applies to references to them, for example shareToPrimarySid and conditionIsMemberOfPrimarySid of the profile share policies or primarySid in the test run of the user synchronisation. Only organisational units have a freely assigned sid.
# Prepare headers for request
$apiRequestHeaders = @{
"Authorization" = "Bearer $accessToken"
}
$sid = Read-Host "Enter SID"
Write-Host "Creating and onboarding user with SID: $sid"
try {
$apiUrl = "https://{instanz}/webapi/api/v3/$($datasourceId)/Admin/Users?primarySid=" + $sid;
$user = Invoke-RestMethod -Uri $apiUrl -Method POST -Headers $apiRequestHeaders
Write-Output "Onboarded '$($user.title)' with id '$($user.id)'";
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
Managing permissions on organizational units
The permissions of an organizational unit control which users, groups or AppUserGroups are allowed to use the organizational unit. The Admin API provides the following endpoints:
| Endpoint | Description |
|---|---|
GET .../Admin/OrganizationUnits/{organizationUnitId} | Returns the details including the permissions list. Each entry contains the id of the permission and exactly one of the IDs userId, groupId or appUserGroupId. |
POST .../Admin/OrganizationUnits/{organizationUnitId}/Permissions | Adds a permission. Specify exactly one of the properties UserId, GroupId or AppUserGroupId in the body. |
DELETE .../Admin/OrganizationUnits/{organizationUnitId}/Permissions/{permissionId} | Removes the specified permission. |
Both writing endpoints return the updated organizational unit including its permissions.
- Only one permission is possible per security account (user, group or AppUserGroup). To change it, remove the existing permission first and then add a new one.
- When creating an organizational unit (
POST .../Admin/OrganizationUnits), permissions can be supplied directly via the optionalPermissionsarray (exactly one of the three IDs per entry, each account at most once).
# Prepare headers for request
$apiRequestHeaders = @{
"Authorization" = "Bearer $accessToken"
"Content-Type" = "application/json"
}
# Add a permission for a group
$body = @{ GroupId = "1de8e5a3-0d5f-4be3-b1ab-1e02c04efeb3" } | ConvertTo-Json
$apiUrl = "https://{instanz}/webapi/api/v3/$($datasourceId)/Admin/OrganizationUnits/$($organizationUnitId)/Permissions"
$organizationUnit = Invoke-RestMethod -Uri $apiUrl -Method POST -Headers $apiRequestHeaders -Body $body
$organizationUnit.permissions | Format-Table
# Remove a permission
$apiUrl = "https://{instanz}/webapi/api/v3/$($datasourceId)/Admin/OrganizationUnits/$($organizationUnitId)/Permissions/$($permissionId)"
$organizationUnit = Invoke-RestMethod -Uri $apiUrl -Method DELETE -Headers $apiRequestHeaders
Managing snippet metadata
In addition to snippet groups, the metadata of individual snippets can be read and maintained via the Admin API. Snippet management in the web admin builds on this. Only the metadata is managed here (name, key, sort order, AutoText, permissions and localizations). A snippet's content and format are read only through these endpoints, not changed.
The endpoints live under .../Admin/Snippets/{usage}/…. {usage} scopes the area and is either public (generally available snippets) or designer (snippets used in template design).
| Endpoint | Description |
|---|---|
GET .../Admin/Snippets/{usage}/{snippetId} | Returns a snippet with its metadata, localizations (localized), permissions (permissions) and the existing content/format information (data, read-only). |
POST .../Admin/Snippets/{usage} | Creates a new, metadata-only snippet. |
PUT .../Admin/Snippets/{usage}/{snippetId} | Changes the metadata (SnippetGroupId, Sort, Key, AutoTextName). |
DELETE .../Admin/Snippets/{usage}/{snippetId} | Deletes the snippet. |
POST .../Admin/Snippets/{usage}/{snippetId}/Permissions | Adds a permission (in the body provide exactly one of the IDs UserId, GroupId or AppUserGroupId, plus HasModifyPermission/HasUsingPermission). |
DELETE .../Admin/Snippets/{usage}/{snippetId}/Permissions/{permissionId} | Removes the given permission. |
POST .../Admin/Snippets/{usage}/{snippetId}/Localized | Adds the localization for one language (Name, Description, SearchKeywords, Lcid). |
DELETE .../Admin/Snippets/{usage}/{snippetId}/Localized/{lcid} | Removes the localization of the given language (lcid). |
All endpoints return the updated snippet. The following rules apply, among others:
Keyis optional, at most 256 characters and unique across all snippets.AutoTextNameis optional and at most 32 characters.Sortis ≥ 0 and unique within the same parent group. An emptySnippetGroupIdplaces the snippet directly at the top level.- Only one localization is allowed per language (
Lcid), and each localization must set at least one ofName,DescriptionorSearchKeywords. - Only one permission is possible per security account (user, group or AppUserGroup).
These endpoints require the SysAdmin or SnippetAdmin role. For an API client, the role from its registration applies, see Roles of the API client.
$apiRequestHeaders = @{
"Authorization" = "Bearer $accessToken"
"Content-Type" = "application/json"
}
# Read a snippet's metadata
$apiUrl = "https://{instanz}/webapi/api/v3/$($datasourceId)/Admin/Snippets/public/$($snippetId)"
$snippet = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
$snippet.localized | Format-Table
# Change metadata (e.g. key and sort order)
$body = @{ Key = "greeting-formal"; Sort = 0 } | ConvertTo-Json
$apiUrl = "https://{instanz}/webapi/api/v3/$($datasourceId)/Admin/Snippets/public/$($snippetId)"
$snippet = Invoke-RestMethod -Uri $apiUrl -Method PUT -Headers $apiRequestHeaders -Body $body
Colour and font themes of an organisational unit
The colour themes and font themes of an organisational unit can be read and set as XML. All endpoints refer to the default document language of the datasource.
| Endpoint | Description |
|---|---|
GET .../Admin/OrganizationUnits/{organizationUnitId}/Themes | Returns the effective themes under color and font, each with xml, isInherited and sourceOrganizationUnitId (the unit the theme comes from). |
PUT .../Admin/OrganizationUnits/{organizationUnitId}/Themes/Color | Sets the colour theme. Body: { "xml": "<ColorThemes>…</ColorThemes>" }. An inherited theme is overridden by it. |
PUT .../Admin/OrganizationUnits/{organizationUnitId}/Themes/Font | Sets the font theme, body accordingly. |
DELETE .../Admin/OrganizationUnits/{organizationUnitId}/Themes/Color | Removes the unit's own colour theme. The unit then inherits from its parent again, and a root unit has none. |
DELETE .../Admin/OrganizationUnits/{organizationUnitId}/Themes/Font | Removes the unit's own font theme. |
All endpoints return the effective themes after the change. The XML is validated before saving. Invalid XML leads to HTTP 400 on PUT, an invalid stored theme to HTTP 422 on GET, an unknown organisational unit to HTTP 404, each as ProblemDetails with code. Concurrent changes overwrite each other, the last one wins.
Translation keys
GET .../Admin/Translations returns all active keys of the global translations with their value in the default UI language of the datasource. If a value is missing there, the value of the first language that has one is returned. The response contains uiLcid and under keys per key key, value and lcid (the language of the reported value, null without any value).
Statistics
The statistics evaluate the usage log. Every «template used» entry (Open) counts as a document creation.
| Endpoint | Description |
|---|---|
GET .../Admin/Statistics/DocumentCreations | Number of created documents per day or week (buckets, periods without creations with 0), plus totalCount and the up to 10 most used templates (topTemplates). |
GET .../Admin/Statistics/TemplateUsages | Per template across all versions: number of created documents (openCount), number of creations, updates and deletions of the template, time of the template's most recent log entry in the range (lastUsedUtc, any action) and the breakdown by organisational unit and document language. Plus under organizationUnits per unit the number of documents and templates used. |
| Parameter | Description |
|---|---|
fromUtc | Start of the period (inclusive, UTC). Default: 12 weeks before the end. |
toUtc | End of the period (exclusive, UTC). Default: now. The period may cover at most 366 days. |
interval | DocumentCreations only: Day or Week (default, weeks start on Monday). |
uiLanguage | Language of the template and group names as IETF tag, for example de or fr-CH. Without a translation the default UI language applies. |
Every template carries a status: Active (at least one version active), Deleted (all versions deleted but still in the database) or Removed (no longer in the database, the name comes from the log). Entries without an organisational unit appear under an empty ID.
The evaluation of the clients and versions in use is found under GET .../Admin/ClientUsages/Summary and GET .../Admin/ClientUsages. Besides the entries per client type and version, the summary contains under activeUsers the number of active users in the last 7, 30 and 60 days. Details under Client usage.
Settings
The settings of the datasource are found under .../Admin/Settings/…. Every area has a GET for reading and a PUT for writing:
| Area | Content |
|---|---|
General | Accent colour of the client, help link and help title, snippet hotkeys (XML) |
ClientSync | Synchronisation interval and maximum delay after primedocs Desktop starts. POST .../ClientSync/CacheReset instructs all clients to discard their cache at the next start. |
Security | privacyLogUserData and logEntryRetentionDays for the usage log, plus whether a datasource key exists and its short form (fingerprint) |
PrincipalConnector | Active connector to the directory, reachability, settings per connector (Active Directory, Microsoft Graph, SCIM with tenantName from the server configuration) and the rule for the display name in userNameRule |
Outlook, Connect, Ribbon, Ai | The settings of the respective area |
ProfileOrganizationValidation | Mode of the profile validation and time of the last run |
The display name rule is saved separately: PUT .../Settings/PrincipalConnector/UserNameRule with expression (building blocks in parts and separator or a JavaScript in script, null removes the rule) and fallbackAttribute (name attribute for empty results). POST .../Settings/PrincipalConnector/UserNameRule/Preview evaluates a draft for one user (primarySid) without saving it. blankAttributes simulates empty attributes.
The API answers for the datasource key itself (Security/EncryptionKey) as well as Security/Encrypt and Security/Decrypt only for signed-in administrators, not for API clients.
Fields
The field definitions of users and organizational units are available under .../Admin/Fields/User and .../Admin/Fields/OrganizationUnit, see Fields in the Web Admin. The SysAdmin role is required.
| Endpoint | Description |
|---|---|
GET .../Fields/User | All user fields |
POST .../Fields/User | Creates a field |
PUT .../Fields/User/{id} | Changes a field. Send the revision from the last response, otherwise the API answers with 409 Conflict if the field has changed in the meantime. |
DELETE .../Fields/User/{id}?revision=… | Deletes an inactive field including all values |
PUT .../Fields/User/Layout | Changes the layout of several fields in one call: fields with id, revision, row, column, sort and optionally group |
The same endpoints exist under .../Fields/OrganizationUnit. A field contains fieldId, type (text, image, checkbox), active, row, column, sort, isRequired, canBeModifiedInProfile, multiline, multilineHeight, textLength, imageType and localizations (per lcid with title, group, description, initialValue). User fields additionally have canBeModifiedInUser and externalReference. fieldId and type cannot be changed after creation. The API does not return fields for colour and font themes.
SID migration
The endpoints of the directory switch are available under .../Admin/PrimarySidMigration/…. They link existing users and groups to their SID in the currently configured directory. The SysAdmin role is required. All endpoints use POST:
| Endpoint | Description |
|---|---|
Users/Preview | Matches all users against the directory via a shared value and proposes the SID from the directory. Body: field (ID of an active user text field, e.g. User.Email) and matchingProperty (property in the directory, e.g. mail). Writes nothing. |
Groups/Preview | Matches all groups by their name. No body. Writes nothing. |
Users/Apply, Groups/Apply | Writes the confirmed SIDs. Body: entries with id, newPrimarySid and expectedPrimarySid (SID from the preview). |
Among other things, the preview returns currentPrimarySid, proposedPrimarySid, status (Found, ToMigrate, NotFound) per account and, for conflicts, suggestion (Preferred, Parked, Undecided), suggestionReason and placeholderPrimarySid. If the directory cannot be reached, this is indicated in directoryAvailable and directoryError.
The response of Apply contains applied (with oldPrimarySid and newPrimarySid) and rejected (with code and message). Valid entries are written even when others are rejected, so always evaluate rejected. If the accounts change during the run, the API answers with 409 Conflict and writes nothing.
Search
GET .../Admin/Search is the search behind the search bar of the Web Admin. It finds users, groups, app groups, organizational units, profile roles and user and organization fields in the datasource by display name, technical ID or SID and translations. One of the roles SysAdmin, OrganisationAdmin or UserAdmin is required. The roles also determine which object types are searched: users, groups and app groups for UserAdmin, organizational units and profile roles for OrganisationAdmin, fields only for SysAdmin.
| Parameter | Description |
|---|---|
query (required) | Search term, at least 2 characters after trimming surrounding whitespace. Shorter terms return an empty list. |
lcid (optional) | Language of the display names, default 1033. If a translation is missing in this language, the closest one applies. |
limit (optional) | Maximum number of hits per object type. Default 5, allowed 1 to 20. |
The response is a list of hits with id, name, type (User, Group, AppUserGroup, OrganizationUnit, ProfileRole, UserField, OrganizationUnitField) and description. The description holds the technical identifier, such as the SID of an account or the ID of a role or field. When a field was found through its group, the group replaces the field ID. Within an object type, exact matches come before matches at the start of the name, and those before matches inside the name. The limit applies after this ranking, so an exact match is never lost.
# Prepare headers for request
$apiRequestHeaders = @{
"Authorization" = "Bearer $accessToken"
}
# Search users, groups, organization units, profile roles and fields
$query = [uri]::EscapeDataString("Müller")
$apiUrl = "https://{instanz}/webapi/api/v3/$($datasourceId)/Admin/Search?query=$query&lcid=2057&limit=5"
$hits = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
$hits | Format-Table type, name, description, id
PowerShell Script
Below you will find a complete example PowerShell script that covers various use cases: users, profiles and profile shares, groups, organizational units, snippet groups (incl. permissions and localizations), AppUserGroups, and templates.
The Configuration section (datasourceId, tokenUrl, baseApiUrl, clientId and clientSecret) must be changed to the settings of your environment.
# Configuration
$datasourceId = "b78c3707-d7c7-4fc7-b97f-87d70f63c1ac"
$tokenUrl = "https://{instanz}/ids/connect/token"
$baseApiUrl = "https://{instanz}/webapi/api"
$clientID = "CustomApiClient"
$clientSecret = "CustomClient_Secret_123"
$scope = "pd_AdminWebApi"
$tokenRequestHeaders = @{
"Content-Type" = "application/x-www-form-urlencoded"
}
$tokenRequestBody = @{
client_id = $clientID
client_secret = $clientSecret
grant_type = "client_credentials"
scope = $scope
}
# Request the access token
try {
$tokenResponse = Invoke-RestMethod -Uri $tokenUrl -Method POST -Headers $tokenRequestHeaders -Body $tokenRequestBody
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
if (-not $tokenResponse.access_token) {
Write-Error "Failed to obtain an access token!"
exit 1
}
# Access token information
Write-Host "Access Token: $($tokenResponse.access_token)"
$accessToken = $tokenResponse.access_token
# Prepare headers for request
$apiRequestHeaders = @{
"Authorization" = "Bearer $accessToken"
}
$putBodyRequestHeaders = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer $accessToken"
}
function Show-Menu {
Write-Host "================ Main Menu ================"
Write-Host "1: Get Users"
Write-Host "2: Get User Details"
Write-Host "3: Update User"
Write-Host "4: Create and Onboard User"
Write-Host "5: Get Profile"
Write-Host "6: Profile Update"
Write-Host "7: Add Profile Share"
Write-Host "8: Delete Profile Share"
Write-Host "9: Create and Onboard Group"
Write-Host "10: Get Groups"
Write-Host "11: Get Organization Units"
Write-Host "12: Get Organization Unit Details"
Write-Host "13: Get Organization Unit Children"
Write-Host "14: Update Organization Unit"
Write-Host "15: Create Organization Unit"
Write-Host "16: Delete Organization Unit"
Write-Host "29: Get Organization Unit Tree"
Write-Host "30: Add Organization Unit Permission"
Write-Host "31: Delete Organization Unit Permission"
Write-Host "17: Get Snippet Groups"
Write-Host "18: Get Snippet Group Details"
Write-Host "19: Create Snippet Group"
Write-Host "20: Delete Snippet Group"
Write-Host "21: Get Snippet Group Children"
Write-Host "22: Update Snippet Group ParentId or Sort"
Write-Host "23: Delete Snippet Group Permission"
Write-Host "24: Add Snippet Group Permission"
Write-Host "25: Delete Snippet Group Localization"
Write-Host "26: Add Snippet Group Localization"
Write-Host "27: Get AppUserGroups"
Write-Host "28: Get Templates"
Write-Host "Q: Exit"
}
function GetUsers {
Write-Host "Getting users..."
$pageSize = Read-Host "Enter pagesize"
$page = Read-Host "Enter page"
$query = Read-Host "Enter query"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/Users?pageSize=$($pageSize)&page=$($page)"
# Append query only if it's not null or whitespace
if (![string]::IsNullOrWhiteSpace($query)) {
$apiUrl += "&query=$query"
}
$response = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
foreach ($user in $response.data) {
Write-Output "$($user.id): '$($user.title)'"
}
Write-Output "Page: $($response.pagingDetails.page) / $($response.pagingDetails.totalPages)"
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function GetUserDetails {
$userId = Read-Host "Enter UserId"
Write-Host "Getting details for user: $userId"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/Users/" + $userId;
$user = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
Write-Output "Id: '$($user.id)'"
Write-Output "Title: '$($user.title)'";
Write-Output "Profiles: "
foreach ($profile in $user.profiles) {
Write-Output "'$($profile.id)': '$($profile.title) (Profile OU: '$($profile.organizationUnitId)')"
}
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function CreateAndOnBoardUser {
$sid = Read-Host "Enter SID"
Write-Host "Creating and onboarding user with SID: $sid"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/Users?primarySid=" + $sid;
$user = Invoke-RestMethod -Uri $apiUrl -Method POST -Headers $apiRequestHeaders
Write-Output "Onboarded '$($user.title)' with id '$($user.id)'";
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function CreateAndOnBoardGroup {
$sid = Read-Host "Enter SID"
Write-Host "Creating and onboarding Group with SID: $sid"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/Groups?primarySid=" + $sid;
$group = Invoke-RestMethod -Uri $apiUrl -Method POST -Headers $apiRequestHeaders
Write-Output "Onboarded '$($group.title)' with id '$($group.id)'";
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function GetGroups {
Write-Host "Getting Groups..."
$pageSize = Read-Host "Enter pagesize"
$page = Read-Host "Enter page"
$query = Read-Host "Enter query"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/Groups?pageSize=$($pageSize)&page=$($page)"
# Append query only if it's not null or whitespace
if (![string]::IsNullOrWhiteSpace($query)) {
$apiUrl += "&query=$query"
}
$response = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
foreach ($group in $response.data) {
Write-Output "$($group.id): '$($group.title)'"
}
Write-Output "Page: $($response.pagingDetails.page) / $($response.pagingDetails.totalPages)"
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function GetAppUserGroups {
Write-Host "Getting AppUserGroups..."
$pageSize = Read-Host "Enter pagesize"
$page = Read-Host "Enter page"
$query = Read-Host "Enter query"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/AppUserGroups?pageSize=$($pageSize)&page=$($page)"
# Append query only if it's not null or whitespace
if (![string]::IsNullOrWhiteSpace($query)) {
$apiUrl += "&query=$query"
}
$response = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
foreach ($group in $response.data) {
Write-Output "$($group.id): '$($group.title)'"
}
Write-Output "Page: $($response.pagingDetails.page) / $($response.pagingDetails.totalPages)"
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function GetTemplates {
Write-Host "Getting Templates..."
$pageSize = Read-Host "Enter pagesize"
$page = Read-Host "Enter page"
$query = Read-Host "Enter query"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/Templates?pageSize=$($pageSize)&page=$($page)"
# Append query only if it's not null or whitespace
if (![string]::IsNullOrWhiteSpace($query)) {
$apiUrl += "&query=$query"
}
$response = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
foreach ($template in $response.data) {
Write-Output "$($template.templateId): MetaTemplateId: '$($template.metaTemplateId)'"
}
Write-Output "Page: $($response.pagingDetails.page) / $($response.pagingDetails.totalPages)"
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function ProfileUpdate {
$userId = Read-Host "Enter User ID"
$profileId = Read-Host "Enter Profile ID"
$title = Read-Host "Enter Title"
$organizationUnitId = Read-Host "Enter New Organization Unit ID"
$lcid = 1031 #Read-Host "Enter lcid"
$field = "Org.Title" # Read-Host "Enter Field"
$field2 = "User.FirstName" # Read-Host "Enter Field"
$text = Read-Host "Enter New Org.Title Value (Empty = delete)"
$text2 = Read-Host "Enter New User.FirstName Value (Empty = delete)"
Write-Host "Updating profile for User ID: $userId, Profile ID: $profileId, Title: $title, Organization Unit ID: $organizationUnitId"
$targetOrganizationUnitId = if ($null -ne $organizationUnitId) {
[System.Guid]::Parse($organizationUnitId)
} else {
$null
}
try {
$body = @{
Title = $title;
OrganizationUnitId = $targetOrganizationUnitId;
FieldChanges = @(
@{
Lcid = $lcid
Field = $field
Text = $text
Purge = ($text -eq "")
},
@{
Lcid = $lcid
Field = $field2
Text = $text2
Purge = ($text2 -eq "")
}
)
}
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/Users/$($userId)/Profiles/$($profileId)";
# Ensure UTF-8 Encoding
$utf8body = ([System.Text.Encoding]::UTF8.GetBytes(($body | ConvertTo-Json)))
Invoke-RestMethod -Uri $apiUrl -Method PUT -Headers $putBodyRequestHeaders -Body $utf8body
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function GetProfile {
$userId = Read-Host "Enter User ID"
$profileId = Read-Host "Enter Profile ID"
Write-Host "Getting profile for User ID: $userId, Profile ID: $profileId"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/Users/" + $userId + "/Profiles/" + $profileId;
$userProfile = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
Write-Output "Id: '$($userProfile.id)'"
Write-Output "Title: '$($userProfile.title)'";
Write-Output "ProfileShares: "
foreach ($share in $userProfile.profileShares) {
if ($null -ne $share.userId) {
Write-Output "'$($share.id)': UserId:'$($share.userId)', ProfilePermissionLevel:'$($share.profilePermissionLevel)', SignaturePermissionLevel:'$($share.signaturePermissionLevel)'"
}
if ($null -ne $share.groupId) {
Write-Output "'$($share.id)': GroupId:'$($share.groupId)', ProfilePermissionLevel:'$($share.profilePermissionLevel)', SignaturePermissionLevel:'$($share.signaturePermissionLevel)'"
}
if ($null -ne $share.appUserGroupId) {
Write-Output "'$($share.id)': AppUserGroupId:'$($share.appUserGroupId)', ProfilePermissionLevel:'$($share.profilePermissionLevel)', SignaturePermissionLevel:'$($share.signaturePermissionLevel)'"
}
}
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function AddProfileShare {
$userId = Read-Host "Enter User ID"
$profileId = Read-Host "Enter Profile ID"
Write-Host "Enter either User ID or Group ID or AppUserGroup ID to access the profile. Leave two of them empty."
$shareUserId = Read-Host "Enter User ID (or empty)"
$shareGroupId = Read-Host "Enter Group ID (or empty)"
$shareAppUserGroupId = Read-Host "Enter AppUserGroup ID (or empty)"
Write-Host " "
Write-Host "0: Profile share cannot be used as 'regular' profile"
Write-Host "1: Profile share can be used as 'regular' profile, but will be marked 'on behalf'"
Write-Host "2: Profile share can be used as 'regular' profile and impersonation is allowed"
$profilePermissionLevel = Read-Host "Enter Profile Permission Level"
Write-Host " "
Write-Host "0: Signature share cannot be used for document signing"
Write-Host "1: Signature share can be used for document signing, but without real signature image"
Write-Host "2: Signature share can be used for document signing with real signature image"
$signaturePermissionLevel = Read-Host "Enter Signature Permission Level"
Write-Host " "
Write-Host "0: Read-only (default) - profile can be used but not edited"
Write-Host "1: Read/write - profile can also be edited (data fields, signatures, organizational unit)"
$accessPermissionLevel = Read-Host "Enter Access Permission Level"
$targetShareUserId = if ($shareUserId -eq "") {
$null
} else {
$shareUserId
}
$targetShareGroupId = if ($shareGroupId -eq "") {
$null
} else {
$shareGroupId
}
$targetShareAppUserGroupId = if ($shareAppUserGroupId -eq "") {
$null
} else {
$shareAppUserGroupId
}
try {
$body = @{
UserId = $targetShareUserId
GroupId = $targetShareGroupId
AppUserGroupId = $targetShareAppUserGroupId
profilePermissionLevel = $profilePermissionLevel
signaturePermissionLevel = $signaturePermissionLevel
accessPermissionLevel = $accessPermissionLevel
}
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/Users/$($userId)/Profiles/$($profileId)/ProfileShares";
# Ensure UTF-8 Encoding
$utf8body = ([System.Text.Encoding]::UTF8.GetBytes(($body | ConvertTo-Json)))
Invoke-RestMethod -Uri $apiUrl -Method POST -Headers $putBodyRequestHeaders -Body $utf8body
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function DeleteProfileShare {
$userId = Read-Host "Enter User ID"
$profileId = Read-Host "Enter Profile ID"
$profileShareId = Read-Host "Enter Profile Share ID"
Write-Host "Deleting profile share for User ID: $userId, Profile ID: $profileId, Profile Share ID: $profileShareId"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/Users/$($userId)/Profiles/$($profileId)/ProfileShares/$($profileShareId)";
Invoke-RestMethod -Uri $apiUrl -Method DELETE -Headers $putBodyRequestHeaders
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function GetOrganizationUnits {
Write-Host "Getting organization units..."
$pageSize = Read-Host "Enter pagesize"
$page = Read-Host "Enter page"
$query = Read-Host "Enter query (optional, e.g. title:*Sales* or sid:OU-123)"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/OrganizationUnits?pageSize=$($pageSize)&page=$($page)";
# Append query only if it's not null or whitespace
if (![string]::IsNullOrWhiteSpace($query)) {
$apiUrl += "&query=$query"
}
$response = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
foreach ($organizationUnit in $response.data) {
Write-Output "$($organizationUnit.id): '$($organizationUnit.title)'"
}
Write-Output "Page: $($response.pagingDetails.page) / $($response.pagingDetails.totalPages)"
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function GetOrganizationUnitDetails {
$organizationUnitId = Read-Host "Enter Organization Unit ID"
Write-Host "Getting details for organization unit: $organizationUnitId"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/OrganizationUnits/" + $organizationUnitId;
$organizationUnit = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
Write-Output "Id: '$($organizationUnit.id)'"
Write-Output "Title: '$($organizationUnit.title)'";
Write-Output "ParentId: '$($organizationUnit.parentId)'";
Write-Output "Fields:";
foreach ($field in $organizationUnit.fields) {
$inherited = if ($field.isInherited) { " (inherited)" } else { "" }
Write-Output " [$($field.lcid)] $($field.fieldId): '$($field.text)'$inherited"
}
Write-Output "Permissions:";
foreach ($permission in $organizationUnit.permissions) {
Write-Output " '$($permission.id)': UserId '$($permission.userId)' | GroupId '$($permission.groupId)' | AppUserGroupId '$($permission.appUserGroupId)'"
}
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function GetOrganizationUnitChildren {
$organizationUnitId = Read-Host "Enter Organization Unit ID"
$pageSize = Read-Host "Enter pagesize"
$page = Read-Host "Enter page"
$searchTerm = Read-Host "Enter title search term (optional)"
Write-Host "Getting children for organization unit: $organizationUnitId"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/OrganizationUnits/" + $organizationUnitId + "/Children?pageSize=$($pageSize)&page=$($page)";
if (![string]::IsNullOrWhiteSpace($searchTerm)) {
$query = [System.Uri]::EscapeDataString("title:*$searchTerm*")
$apiUrl += "&query=$query"
}
$response = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
foreach ($organizationUnit in $response.data) {
Write-Output "$($organizationUnit.id): '$($organizationUnit.title)'"
}
Write-Output "Page: $($response.pagingDetails.page) / $($response.pagingDetails.totalPages)"
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function GetOrganizationUnitTree {
$searchTerm = Read-Host "Enter title search term (optional)"
Write-Host "Getting organization unit tree..."
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/OrganizationUnitTree"
if (![string]::IsNullOrWhiteSpace($searchTerm)) {
$query = [System.Uri]::EscapeDataString("title:*$searchTerm*")
$apiUrl += "?query=$query"
}
$response = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
$response | ConvertTo-Json -Depth 10
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function UpdateOrganizationUnit {
Write-Host "Updating organization unit"
$organizationUnitId = Read-Host "Enter Organization Unit ID"
$title = Read-Host "Enter New Title"
$parentId = Read-Host "Enter New Parent Organization Unit ID"
$sid = Read-Host "Enter New SID"
$lcid = 1031 #Read-Host "Enter lcid"
$field = "Org.Title" # Read-Host "Enter Field"
$field2 = "Org.Claim" # Read-Host "Enter Field"
$text = Read-Host "Enter New Org.Title Value (Empty = delete)"
$text2 = Read-Host "Enter New Org.Claim Value (Empty = delete)"
Write-Host "Updating organization unit, Title: $title, Organization Unit ID: $organizationUnitId"
$targetParentId = if ($null -ne $parentId) {
[System.Guid]::Parse($parentId)
} else {
$null
}
try {
$body = @{
Title = $title
ParentId = $targetParentId
Sid = $sid
FieldChanges = @(
@{
Lcid = $lcid
Field = $field
Text = $text
Purge = ($text -eq "")
},
@{
Lcid = $lcid
Field = $field2
Text = $text2
Purge = ($text2 -eq "")
}
)
}
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/OrganizationUnits/$($organizationUnitId)";
# Ensure UTF-8 Encoding
$utf8body = ([System.Text.Encoding]::UTF8.GetBytes(($body | ConvertTo-Json)))
Invoke-RestMethod -Uri $apiUrl -Method PUT -Headers $putBodyRequestHeaders -Body $utf8body
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function CreateOrganizationUnit {
Write-Host "Creating organization unit"
$title = Read-Host "Enter Title"
$parentId = Read-Host "Enter Parent Organization Unit ID (leave empty for root)"
$sid = Read-Host "Enter SID (optional, leave empty to create without SID)"
$lcid = 1031 #Read-Host "Enter lcid"
$field = "Org.Title" # Read-Host "Enter Field"
$text = Read-Host "Enter Org.Title Value (leave empty to skip)"
$targetParentId = if (![string]::IsNullOrWhiteSpace($parentId)) {
[System.Guid]::Parse($parentId)
} else {
$null
}
$targetSid = if (![string]::IsNullOrWhiteSpace($sid)) { $sid } else { $null }
$fields = @()
if (![string]::IsNullOrWhiteSpace($text)) {
$fields += @{
Lcid = $lcid
Field = $field
Text = $text
}
}
try {
$body = @{
Title = $title
ParentId = $targetParentId
Sid = $targetSid
Fields = $fields
}
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/OrganizationUnits";
# Ensure UTF-8 Encoding
$utf8body = ([System.Text.Encoding]::UTF8.GetBytes(($body | ConvertTo-Json)))
$newId = Invoke-RestMethod -Uri $apiUrl -Method POST -Headers $putBodyRequestHeaders -Body $utf8body
Write-Output "Created OrganizationUnit with ID: $newId"
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function DeleteOrganizationUnit {
Write-Host "Deleting organization unit"
$organizationUnitId = Read-Host "Enter Organization Unit ID"
$deleteChildrenInput = Read-Host "Delete children as well? (y/n, default: n)"
$deleteChildren = ($deleteChildrenInput -eq "y")
Write-Host "Deleting organization unit: $organizationUnitId (deleteChildren: $deleteChildren)"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/OrganizationUnits/$($organizationUnitId)?deleteChildren=$($deleteChildren)";
Invoke-RestMethod -Uri $apiUrl -Method DELETE -Headers $apiRequestHeaders
Write-Output "Deleted OrganizationUnit: $organizationUnitId"
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function AddOrganizationUnitPermission {
$organizationUnitId = Read-Host "Enter Organization Unit ID"
Write-Host "Enter either User ID or Group ID or AppUserGroup ID to allow using the organization unit. Leave two of them empty."
$shareUserId = Read-Host "Enter User ID (or empty)"
$shareGroupId = Read-Host "Enter Group ID (or empty)"
$shareAppUserGroupId = Read-Host "Enter AppUserGroup ID (or empty)"
$targetUserId = if ($shareUserId -eq "") { $null } else { $shareUserId }
$targetGroupId = if ($shareGroupId -eq "") { $null } else { $shareGroupId }
$targetAppUserGroupId = if ($shareAppUserGroupId -eq "") { $null } else { $shareAppUserGroupId }
try {
$body = @{
UserId = $targetUserId
GroupId = $targetGroupId
AppUserGroupId = $targetAppUserGroupId
}
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/OrganizationUnits/$($organizationUnitId)/Permissions";
# Ensure UTF-8 Encoding
$utf8body = ([System.Text.Encoding]::UTF8.GetBytes(($body | ConvertTo-Json)))
$organizationUnit = Invoke-RestMethod -Uri $apiUrl -Method POST -Headers $putBodyRequestHeaders -Body $utf8body
Write-Output "Permissions:"
foreach ($permission in $organizationUnit.permissions) {
Write-Output " '$($permission.id)': UserId '$($permission.userId)' | GroupId '$($permission.groupId)' | AppUserGroupId '$($permission.appUserGroupId)'"
}
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function DeleteOrganizationUnitPermission {
$organizationUnitId = Read-Host "Enter Organization Unit ID"
$permissionId = Read-Host "Enter Permission ID"
Write-Host "Deleting permission $permissionId on organization unit: $organizationUnitId"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/OrganizationUnits/$($organizationUnitId)/Permissions/$($permissionId)";
Invoke-RestMethod -Uri $apiUrl -Method DELETE -Headers $apiRequestHeaders
Write-Output "Deleted permission: $permissionId"
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function UpdateUser {
$userId = Read-Host "Enter User ID"
$title = Read-Host "Enter New Title"
$lcid = 1031 #Read-Host "Enter lcid"
$field = "User.FirstName" # Read-Host "Enter Field"
$field2 = "User.LastName" # Read-Host "Enter Field"
$text = Read-Host "Enter New User.FirstName Value (Empty = delete)"
$text2 = Read-Host "Enter New User.LastName Value (Empty = delete)"
Write-Host "Updating user, Title: $title, User ID: $userId"
try {
$body = @{
Title = $title
FieldChanges = @(
@{
Lcid = $lcid
Field = $field
Text = $text
Purge = ($text -eq "")
},
@{
Lcid = $lcid
Field = $field2
Text = $text2
Purge = ($text2 -eq "")
}
)
}
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/Users/$($userId)";
# Ensure UTF-8 Encoding
$utf8body = ([System.Text.Encoding]::UTF8.GetBytes(($body | ConvertTo-Json)))
Invoke-RestMethod -Uri $apiUrl -Method PUT -Headers $putBodyRequestHeaders -Body $utf8body
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function GetSnippetGroups {
Write-Host "Getting snippet groups (public)..."
$pageSize = Read-Host "Enter pagesize"
$page = Read-Host "Enter page"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/SnippetGroups/Public/?pageSize=$($pageSize)&page=$($page)";
$response = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
foreach ($snippetGroup in $response.data) {
Write-Output "$($snippetGroup.id)"
}
Write-Output "Page: $($response.pagingDetails.page) / $($response.pagingDetails.totalPages)"
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function GetSnippetGroupsChildren {
Write-Host "Getting snippet groups (public)..."
$parentSnippetGroup = Read-Host "Enter Parent Snippet Group"
$pageSize = Read-Host "Enter pagesize"
$page = Read-Host "Enter page"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/SnippetGroups/Public/$($parentSnippetGroup)/SnippetGroups?pageSize=$($pageSize)&page=$($page)";
$response = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
foreach ($snippetGroup in $response.data) {
Write-Output "$($snippetGroup.id)"
}
Write-Output "Page: $($response.pagingDetails.page) / $($response.pagingDetails.totalPages)"
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function GetSnippetGroupDetails {
Write-Host "Getting snippet groups (public) for id..."
$snippetGroup = Read-Host "Enter Snippet Group ID"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/SnippetGroups/Public/$($snippetGroup)";
$snippetGroup = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
Write-Output "Id: '$($snippetGroup.id)'"
Write-Output "ParentId: '$($snippetGroup.parentId)'"
Write-Output "Sort: '$($snippetGroup.sort)'"
Write-Output "Localized Data:"
foreach ($localized in $snippetGroup.localized) {
Write-Output "Localized '$($localized.lcid)': Name '$($localized.name)' | Description '$($localized.description)' | SearchKeywords '$($localized.searchkeywords)'"
}
Write-Output "Permissions:"
foreach ($permission in $snippetGroup.permissions) {
Write-Output "Permission '$($permission.id)': UserId '$($permission.userId)' | GroupId '$($permission.groupid)' | AppUserGroupId '$($permission.appUserGroupId)' | HasModifyPermission: '$($permission.HasModifyPermission)' | HasUsingPermission: '$($permission.HasUsingPermission)'"
}
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function CreateSnippetGroup {
$parentId = Read-Host "Enter Snippet Parent Id (or empty)"
Write-Host "Enter either User ID or Group ID or AppUserGroup ID to give use and modify permission on the snippetgroup. Leave two of them empty."
$shareUserId = Read-Host "Enter User ID (or empty)"
$shareGroupId = Read-Host "Enter Group ID (or empty)"
$shareAppUserGroupId = Read-Host "Enter AppUserGroup ID (or empty)"
$targetShareUserId = if ($shareUserId -eq "") {
$null
} else {
$shareUserId
}
$targetShareGroupId = if ($shareGroupId -eq "") {
$null
} else {
$shareGroupId
}
$targetShareAppUserGroupId = if ($shareAppUserGroupId -eq "") {
$null
} else {
$shareAppUserGroupId
}
$targetParentSnippetGroupId = if ($parentId -eq "") {
$null
} else {
[System.Guid]::Parse($parentId)
}
$lcid = Read-Host "Enter lcid"
$name = Read-Host "Enter Sample Name"
# Sort can also be applied
try {
$body = @{
ParentId = $targetParentSnippetGroupId
Localized = @(
@{
Lcid = $lcid
Name = $name
}
)
Permissions = @(
@{
UserId = $targetShareUserId
GroupId = $targetShareGroupId
AppUserGroupId = $targetShareAppUserGroupId
HasModifyPermission = $true
HasUsingPermission = $true
}
)
}
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/SnippetGroups/Public/";
# Ensure UTF-8 Encoding
$utf8body = ([System.Text.Encoding]::UTF8.GetBytes(($body | ConvertTo-Json)))
Invoke-RestMethod -Uri $apiUrl -Method POST -Headers $putBodyRequestHeaders -Body $utf8body
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function DeleteSnippetGroup {
$snippetGroup = Read-Host "Enter SnippetGroup ID"
Write-Host "Deleting public snippet group (and everything below) for Snippet Group ID: $snippetGroup"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/SnippetGroups/Public/$($snippetGroup)";
Invoke-RestMethod -Uri $apiUrl -Method DELETE -Headers $putBodyRequestHeaders
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function UpdateSnippetGroup {
$targetSnippetGroup = Read-Host "Enter Snippet Group Id which needs to be updated"
$parentId = Read-Host "Enter new Snippet Group Parent Id"
$sort = Read-Host "Enter Sort"
$targetParentSnippetGroupId = if ($parentId -eq "") {
$null
} else {
[System.Guid]::Parse($parentId)
}
try {
$body = @{
ParentId = $targetParentSnippetGroupId
Sort = $sort
}
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/SnippetGroups/Public/$($targetSnippetGroup)";
# Ensure UTF-8 Encoding
$utf8body = ([System.Text.Encoding]::UTF8.GetBytes(($body | ConvertTo-Json)))
Invoke-RestMethod -Uri $apiUrl -Method PUT -Headers $putBodyRequestHeaders -Body $utf8body
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function DeleteSnippetGroupPermission {
$targetSnippetGroup = Read-Host "Enter Snippet Group Id which needs to be updated"
$targetSnippetGroupPermissionId = Read-Host "Enter Permission Id"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/SnippetGroups/Public/$($targetSnippetGroup)/Permissions/$($targetSnippetGroupPermissionId)";
Invoke-RestMethod -Uri $apiUrl -Method DELETE -Headers $putBodyRequestHeaders
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function AddSnippetGroupPermission {
$targetSnippetGroupId = Read-Host "Enter Snippet Group Id"
Write-Host "Enter either User ID or Group ID or AppUserGroup ID to give use and modify permission on the snippetgroup. Leave two of them empty."
$shareUserId = Read-Host "Enter User ID (or empty)"
$shareGroupId = Read-Host "Enter Group ID (or empty)"
$shareAppUserGroupId = Read-Host "Enter AppUserGroup ID (or empty)"
$targetShareUserId = if ($shareUserId -eq "") {
$null
} else {
$shareUserId
}
$targetShareGroupId = if ($shareGroupId -eq "") {
$null
} else {
$shareGroupId
}
$targetShareAppUserGroupId = if ($shareAppUserGroupId -eq "") {
$null
} else {
$shareAppUserGroupId
}
try {
$body = @{
UserId = $targetShareUserId
GroupId = $targetShareGroupId
AppUserGroupId = $targetShareAppUserGroupId
HasModifyPermission = $true
HasUsingPermission = $true
}
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/SnippetGroups/Public/$($targetSnippetGroupId)/Permissions"
# Ensure UTF-8 Encoding
$utf8body = ([System.Text.Encoding]::UTF8.GetBytes(($body | ConvertTo-Json)))
Invoke-RestMethod -Uri $apiUrl -Method POST -Headers $putBodyRequestHeaders -Body $utf8body
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function DeleteSnippetGroupLocalization {
$targetSnippetGroup = Read-Host "Enter Snippet Group Id which needs to be updated"
$targetSnippetGroupLcid = Read-Host "Enter Localization LCID"
try {
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/SnippetGroups/Public/$($targetSnippetGroup)/Localized/$($targetSnippetGroupLcid)";
Invoke-RestMethod -Uri $apiUrl -Method DELETE -Headers $putBodyRequestHeaders
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
function AddSnippetGroupLocalization {
$targetSnippetGroupId = Read-Host "Enter Snippet Group Id"
$lcid = Read-Host "Enter lcid"
$name = Read-Host "Enter Name"
$description = Read-Host "Enter Description"
$searchKeyWord = Read-Host "Enter SearchKeywords"
try {
$body = @{
Lcid = $lcid
Name = $name
Description = $description
SearchKeywords = $searchKeyWord
}
$apiUrl = "$($baseApiUrl)/v3/$($datasourceId)/Admin/SnippetGroups/Public/$($targetSnippetGroupId)/Localized"
# Ensure UTF-8 Encoding
$utf8body = ([System.Text.Encoding]::UTF8.GetBytes(($body | ConvertTo-Json)))
Invoke-RestMethod -Uri $apiUrl -Method POST -Headers $putBodyRequestHeaders -Body $utf8body
}
catch {
Write-Error "Error making the request: $_"
exit 1;
}
}
do {
Show-Menu
$selection = Read-Host "Please select an option"
switch ($selection) {
'1' { GetUsers }
'2' { GetUserDetails }
'3' { UpdateUser }
'4' { CreateAndOnBoardUser }
'5' { GetProfile }
'6' { ProfileUpdate }
'7' { AddProfileShare }
'8' { DeleteProfileShare }
'9' { CreateAndOnBoardGroup }
'10' { GetGroups }
'11' { GetOrganizationUnits }
'12' { GetOrganizationUnitDetails }
'13' { GetOrganizationUnitChildren }
'14' { UpdateOrganizationUnit }
'15' { CreateOrganizationUnit }
'16' { DeleteOrganizationUnit }
'29' { GetOrganizationUnitTree }
'30' { AddOrganizationUnitPermission }
'31' { DeleteOrganizationUnitPermission }
'17' { GetSnippetGroups }
'18' { GetSnippetGroupDetails }
'19' { CreateSnippetGroup }
'20' { DeleteSnippetGroup }
'21' { GetSnippetGroupsChildren }
'22' { UpdateSnippetGroup }
'23' { DeleteSnippetGroupPermission }
'24' { AddSnippetGroupPermission }
'25' { DeleteSnippetGroupLocalization }
'26' { AddSnippetGroupLocalization }
'27' { GetAppUserGroups }
'28' { GetTemplates }
'Q' { break; }
Default {
Write-Host "Invalid option selected"
}
}
} while ($selection -ne 'Q')
Error handling
The HTTP status codes and the error response format are described uniformly under API → Error handling. Missing rights (role SysAdmin or OrganisationAdmin) result in 403 Forbidden, and an unknown datasourceId results in 404 Not Found.
In addition, the following applies to the Admin API:
- Concurrent modification: If a record was changed or deleted by someone else while saving, the API answers with
409 ConflictasProblemDetails(application/problem+json) withcodeConcurrentModification. Nothing was saved. Reload the record and repeat the call. - Deleting users and groups: The calling user must hold every role of the target account, directly or through app groups, otherwise the API answers with
403 Forbidden. The same applies to changes to the members, rule or execution of an app group.SysAdminalways may, the basic roleUserdoes not count. The API rejects deleting the last holder of theSysAdminrole with400 Bad Request. - Timestamps are returned as UTC.
Swagger / Open API
The current Admin API Open API can be used as a reference, for example from the PrimeSoft instance:
https://primesoft.primedocs.io/webapi/swagger/index.html
You can visualise this description using the Swagger Editor, for example.